Developer

# Component playground

All UI Forms Theme generator
Edit any code block live — type into it and the preview above updates in real time. Or click Copy to grab the HTML for your own page. Every component uses the v4 design tokens, so it'll match your theme out of the box.

## Buttons

## Status & badges

Active Pending Failed Info Tag Removable

## Alerts

Saved! Changes have been published.
Heads up. API quota at 87%.
Payment failed. Retry to continue.

## Cards

Plain card
A simple card with header and body. The most common building block.
With subtitle
Plus an action
Header has room for actions on the right.

## Form inputs

Text
Select
Switch Enabled
Segmented
Day Week Month

## Advanced form controls

<p>Click any button in the toolbar — <strong>bold</strong>, <em>italic</em>, lists, links, code blocks. Try <kbd>⌘B</kbd> / <kbd>⌘I</kbd> / <kbd>⌘K</kbd>.</p>

## Tables

Name | Plan | Status | MRR
--- | --- | --- | ---
SK Sarah K. | Pro | Active | $199
MR Michael R. | Business | Active | $499
EW Emily W. | Starter | Pending | $49

## Tabs

## Progress

Storage 62 / 100 GB
API quota 87,400 / 100,000

## Stat tiles

Active users
8,432↑ 14%
vs last week
Churn
2.4%↓ 0.4pp
improving

## Timeline

Just now
Deployment succeeded
v4.0.2 in production · 28s
14 min ago
PR #248 merged
3 hours ago
Review requested

## Accordion

First panel
Content for the first panel goes here.
Second panel
Click the chevron to expand.

## Empty state

No messages yet
When you receive messages, they'll show up here.
Async patterns. The four states every list/grid passes through: loading → loaded, or loading → empty, or loading → error. Wire all four and your app feels solid even on flaky networks. The data-adapter pattern in `src/v4/data-adapter.js` hands you the data; the components below are what you render in each state.

## Skeleton table

Render placeholder rows while data loads. Width variation across columns prevents the "jelly bean" look. Fades in via the `.skeleton` shimmer animation defined in `_components.scss`.

Customer | Plan | Status | MRR
--- | --- | --- | ---

## Skeleton tiles

Use for card grids — dashboards, file managers, product galleries. Avatar circle + headline + 2 lines is the canonical shape.

## List lifecycle — interactive

Click a button below to simulate each state. This is exactly what orders.html and the inbox use under the hood.

## Submit spinner

Disable the button + swap label for a spinner during the request. Re-enable + restore label when it resolves. Click below to simulate a 1.5s submit.

↑ Click "Save"

## Banners — feedback at the page level

For things the user needs to see and may want to act on (failed payment, quota near limit). For ephemeral feedback (saved, copied, deleted) use a toast instead — see general elements.

Saved. Changes are live.
Heads up. Maintenance window scheduled for Friday 02:00 UTC.
API quota at 87%. You'll hit the cap by Friday.
Payment failed. Update your card to keep your subscription active.

## Markup helpers — stop hand-writing scaffolds

For pages that build content from data (orders, inbox threads, kanban cards…) the boilerplate adds up. `src/v4/markup.js` exposes pure functions that return HTML strings — no framework, no virtual DOM. Auto-escapes user content. Drop the result into `innerHTML` or interpolate into a template literal.

Each helper below shows the call site (top) and the rendered output (bottom).

### statTile()

    import { statTile } from '/src/v4/markup.js';

    document.querySelector('#stats').innerHTML = [
      statTile({ label: 'Revenue', value: '$84,520', color: 'green',
                 change: { pct: '+18%', direction: 'up' }, subtext: '$3,218 today' }),
      statTile({ label: 'Pending', value: '42', color: 'yellow',
                 change: { pct: '-3%', direction: 'down' }, subtext: '5 awaiting payment' })
    ].join('');

### statusBadge()

    statusBadge('Paid', 'green')
    statusBadge('Pending', 'yellow')
    statusBadge('Failed', 'red')

### customerCell() — table cell with avatar + name

    tbody.innerHTML = orders.map((o) =>
      `<tr><td>${customerCell({ name: o.name, avatarColor: o.color })}</td></tr>`
    ).join('');

### activityItem() — feeds, audit logs

    const html = items.map((i) => activityItem({
      initials: i.initials, avatarBg: i.bg,
      bodyHtml: `<strong>${escapeHtml(i.user)}</strong> ${escapeHtml(i.text)}`,
      time: i.time
    })).join('');
    container.innerHTML = `<ul class="activity-list">${html}</ul>`;

### visitorRow() — distribution bars

    const html = data.map((d) =>
      visitorRow({ name: d.country, pct: d.pct, flag: d.flag })
    ).join('');

### emptyState() — fallback for no results

    container.innerHTML = emptyState({
      title: 'No orders yet',
      desc: "Orders will appear here once your first customer checks out.",
      iconHtml: '<svg ...></svg>',
      actionHtml: '<button class="btn btn-primary">Create test order</button>'
    });