Components / Foundations
UI foundations
Actions, identity and feedback using existing theme tokens. Try the states, then expand the markup to reuse each pattern.
Design tokens
A compact reference for type, colour, geometry and interface icons.
Type scale
text-3xl
Page heading
text-xl
Section heading
text-sm
Operational copy stays compact and readable.
font-mono
RUN-081 · 08:42 UTC
Semantic colour
-
--dd-canvasPage background -
--dd-surfaceWorking surface -
--dd-inkPrimary text and action -
--dd-accentFocus and emphasis
Spacing & geometry
Use the Tailwind spacing scale, a 44px practical control
target and --dd-radius for restrained rounding.
Borders separate routine regions; reserve shadows for
overlays.
Icon contract
Use 20px outline icons with a consistent stroke. Decorative icons stay hidden from assistive technology; icon-only controls require an accessible name and 44px target.
Buttons
One primary action, secondary controls and explicit disabled or busy states.
Ready. Saving simulates a one-second request.
Deploy is unavailable until the project has an environment.
Markup & reuse: buttons
dd-button provides the 44px target; add
dd-primary for emphasis. Keep icon-only controls
named. A toggle retains its label and changes
aria-pressed. Set disabled and
aria-busy during a request, announce the result
outside the button. The opt-in
createButtonLoading
helper runs an async task with an abort signal, blocks repeat
clicks and restores the button after success or failure. Reset
cancels this simulated request; navigation and toggle buttons
never enter loading automatically.
<button type="button" class="dd-button dd-primary">Save</button>
<button type="button" class="dd-button" disabled>Deploy</button>
<button type="button" class="dd-button"
aria-label="Pin example" aria-pressed="false">…</button>
<p role="status"></p>
<button id="save" type="button" class="dd-button dd-primary"
data-loading-label="Saving…">
<span data-button-spinner hidden aria-hidden="true"
class="h-4 w-4 shrink-0 rounded-full border-2 border-current border-r-transparent motion-safe:animate-spin"></span>
<span data-button-label>Save</span>
</button>
<p id="save-status" role="status"></p>
import { createButtonLoading } from './src/theme/ts/button-loading';
const button = document.querySelector('#save');
const status = document.querySelector('#save-status');
const loading = createButtonLoading(button);
button.addEventListener('click', async () => {
if (loading.busy) return;
status.textContent = 'Saving…';
try {
// Replace persistChanges with your own async operation.
const completed = await loading.run(signal => persistChanges(signal));
if (completed) status.textContent = 'Saved.';
} catch {
status.textContent = 'Could not save. Please try again.';
}
});
// loading.reset() aborts the signal and restores the button.
// Respect that signal in your task; cancellation cannot undo saved work.
Avatars & status
A name remains readable when the image or color is unavailable.
-
ActiveJamie Davis
Workspace owner · Online
-
Pending
Alex Chen
Invitation sent
Markup & reuse: identity
Use an empty image alternative when the adjacent name
identifies the person. Initials are a manually supplied
fallback, not an automatic error handler.
dd-status adds a decorative dot; the text conveys
the state. The photo presence indicator sits in the
bottom-right corner with a surface-colored separating ring.
Always pair it with readable presence text; presence and
account access are separate states.
<span class="relative shrink-0">
<img src="./images/jamie-davis.jpg" alt=""
width="44" height="44" class="h-11 w-11 rounded-md object-cover">
<span aria-hidden="true" class="absolute -bottom-0.5 -right-0.5 h-3.5 w-3.5 rounded-full border-2 border-surface bg-success"></span>
</span>
<span>Jamie Davis</span>
<span>Online</span>
<!-- For away or offline use bg-warning or bg-muted AND update the text. -->
Feedback
Inline feedback stays next to the action. Critical changes should never disappear on a timer.
Review required
A maintainer must approve this change before it can run.
Markup & reuse: feedback
Use a polite status for completion and an alert for urgent errors. Keep live regions in the DOM before updating their text. Static notices need no live-region role. This example has no toast queue or notification service.
<p role="status" class="text-sm text-success"></p>
<p role="alert" class="text-sm text-warning"></p>
Data states
Known progress, indeterminate loading and an empty result with a recovery action.
Fixed specimen; no files are created.
Loading recent runs…
Held loading example. Change the selector to leave this state.
No runs match these filters
Try a wider date range or clear the current filters.
Progress specimen selected.
Markup & reuse: data states
Label native progress with completed and total work. Set
aria-busy on the loading region, hide decorative
skeletons from assistive technology and place announcements
outside that busy region. Skeletons are static and need no
animation.
<label for="export-progress">Preparing export: 3 of 5 files</label>
<progress id="export-progress" value="3" max="5">60%</progress>
<section aria-busy="true" aria-label="Recent runs">
<p>Loading recent runs…</p>
</section>
<p role="status"></p>