PAD + web components

Cards with slots

{card title='Revenue'} is a PAD custom tag that writes <pad-card> with its shadow root: encapsulated styles inlined by {shadow css=}, named slots the browser fills from the light DOM - and not a line of JavaScript.

{shadow}_tags/card.pad{parms} shadowrootmode<slot>::slotted
Live
Revenue

€ 48,210

12% more than last month

Details →
Visitors

12,804

Most of them on a phone

Details →
Returns

38

Three more than usual

Details →

Open the developer tools: each card is <pad-card> with a #shadow-root PAD wrote. The page loads no script for it - the browser builds the root from the <template shadowrootmode>, and the styles inside it neither leak out nor let the page's in.

How it is built

PAD, on the server the browser

<div class="grid">
{cards}
  {card title=$title, tone=$tone}
    <p class="stat">{$value}</p>
    <p class="muted">{$note}</p>
    <a slot="footer" href="?examples/cards">Details →</a>
  {/card}
{/cards}
</div>

<p class="muted small" style="margin-top: 18px">
  Open the developer tools: each card is <code>&lt;pad-card&gt;</code> with a <code>#shadow-root</code> PAD wrote. The page
  loads no script for it - the browser builds the root from the <code>&lt;template shadowrootmode&gt;</code>, and the
  styles inside it neither leak out nor let the page's in.
</p>
<?php

  // Three cards from one custom tag: {card} writes a <pad-card> with its shadow root, the
  // stylesheet inlined by {shadow css=} and named slots; the page hands it the title and the
  // tone as parameters and the rest as content - the light DOM the browser slots in.

  $cards = [ [ 'title' => 'Revenue',  'tone' => 'good', 'value' => '€ 48,210', 'note' => '12% more than last month' ],
             [ 'title' => 'Visitors', 'tone' => 'info', 'value' => '12,804',   'note' => 'Most of them on a phone' ],
             [ 'title' => 'Returns',  'tone' => 'bad',  'value' => '38',       'note' => 'Three more than usual' ] ];

?>
{parms title, tone='info'}
<pad-card tone="{#tone}">
  {shadow css='www:elements/card.css'}
    <header part="head"><slot name="title"></slot></header>
    <div class="body"><slot></slot></div>
    <footer part="foot"><slot name="footer"></slot></footer>
  {/shadow}
  <span slot="title">{#title}</span>
  @content@
</pad-card>
/* The shadow root of <pad-card>, inlined into it by {shadow css=}. Nothing of the page's
   stylesheet reaches in - but custom properties do, so the card follows the page's theme. */

:host {
  --tone: var(--accent, #e8590c);
  display: block;
  border-radius: 16px;
  background: var(--surface, #fff);
  border: 1px solid var(--line, #ddd);
  border-top: 4px solid var(--tone);
  box-shadow: var(--shadow, none);
  overflow: hidden;
}
:host([tone="good"]) { --tone: var(--good, #108a4a); }
:host([tone="bad"])  { --tone: var(--bad, #c93838); }
:host([tone="info"]) { --tone: var(--accent, #e8590c); }

header { padding: 14px 18px 0; font: 700 12px/1.4 system-ui, sans-serif; letter-spacing: .08em; text-transform: uppercase; color: var(--tone); }
.body { padding: 4px 18px 10px; }
footer { padding: 10px 18px; border-top: 1px solid var(--line, #ddd); font-size: 14px; }
::slotted([slot="footer"]) { color: var(--tone); font-weight: 600; text-decoration: none; }