A light-DOM, token-based design system with shadcn/ui's look (new-york-v4, neutral): plain CSS in @layer al, plus small scripts that progressively enhance the interactive elements. This page links the built al.css and al.js and shows every element's demo as it renders. The per-element pages, which also show the no-JavaScript fallbacks, are in demo/. Source and docs: github.com/tapegram/alder-ui.
The colours of the selected theme (Zinc, shadcn's neutral theme, by default). Switch themes above, or re-theme by pasting a shadcn or tweakcn export after al.css.
--background--foreground--card--card-foreground--popover--popover-foreground--primary--primary-foreground--secondary--secondary-foreground--muted--muted-foreground--accent--accent-foreground--destructive--border--input--ring--chart-1--chart-2--chart-3--chart-4--chart-5--sidebar--sidebar-foreground--sidebar-primary--sidebar-primary-foreground--sidebar-accent--sidebar-accent-foreground--sidebar-border--sidebar-ring--destructive-foregroundThe markup Alder renders for <al-input field="title" label="Title" />: a real <label> around the input, then the help text (when given) and the ul.errors list. help is a <small class="help">; invalid colours the border.
The markup Alder renders: a <small class="help"> after the label, named by the control's aria-describedby so a screen reader reads it.
The attribute alone also draws the text (but it is not announced):
The markup Alder renders for <al-textarea field="body" label="Body" />.
The markup Alder renders: a <small class="help"> after the label, named by the control's aria-describedby so a screen reader reads it.
The attribute alone also draws the text (but it is not announced):
The markup Alder renders for <al-select field="kind" label="Kind" />. The native select keeps its arrow and keyboard behaviour.
The markup Alder renders: a <small class="help"> after the label, named by the control's aria-describedby so a screen reader reads it.
The attribute alone also draws the text (but it is not announced):
The markup Alder renders for <al-checkbox field="done" label="Done" /> on a Bool field. The box is drawn before the text.
The markup Alder renders: a <small class="help"> after the label, named by the control's aria-describedby so a screen reader reads it.
The attribute alone also draws the text (but it is not announced):
Styles the <table> inside it; scrolls sideways when the table is wider than the page.
| Title | Status | Comments |
|---|---|---|
| Hello, world | 3 | |
| Drafts are fine | 0 | |
| On tokens | 12 | |
| Old news | 1 |
| Title | Status | Comments |
|---|---|---|
| Hello, world | 3 | |
| Drafts are fine | 0 | |
| On tokens | 12 | |
| Old news | 1 |
| Title | Status | Comments |
|---|---|---|
| Hello, world | 3 | |
| Drafts are fine | 0 | |
| On tokens | 12 | |
| Old news | 1 |
| Title | Status | Comments |
|---|---|---|
| Hello, world | 3 | |
| Drafts are fine | 0 | |
| On tokens | 12 | |
| Old news | 1 |
A bordered surface. Children are placed by slot, whatever their order in the source: header, description and action (the title area), the body, footer, then actions.
The first post, in brief.
The first post. Cards hold any content; the body is everything without a slot.
Posted on 12 October 2026
A leading h3 with no slot is the title, and a p right after it the description.
Styled like the slotted header and description.
The body.
Just a surface.
A small pill for a status or a count.
A message box. Write role="alert" (warning, destructive) or role="status" (info, success) yourself: CSS cannot add a role. heading is drawn as the heading. dismissible makes room for the close button that the optional al-alert.js module adds.
An alert whose only child is an empty ul.errors is not drawn (Alder's <al-alert variant="destructive"><Errors form /></al-alert> before any error). Below: the empty one (hidden), then one with an error.
Lays out the links the app renders: previous, then "Page N of M" from the attributes, then next. A side with nowhere to go is a <span aria-disabled="true">. Children without a slot go in the middle and replace the generated text.
Unslotted links are square buttons; aria-current="page" outlines the current one.
Lays its children out in a column (the default) or a wrapping row, with a gap.
The hidden input, the empty alert and the closed dialog take no space, so the gap between One and Two is one lg gap.
One
Two
The content frame: a centred column of a set maximum width. Put one directly in <body>; this demo nests them to compare the widths.
narrow: at most 40rem
normal (the default): at most 56rem
wide: at most 80rem
Flow content in a page gets shadcn's typography: headings, paragraphs, links, code, quotes, lists, rules and tables.
A quoted passage.
| Name | Value |
|---|---|
| Alpha | One |
| Beta | Two |
The details:
A modal dialog around a native <dialog> that you write: Alder's renderer writes it for you (heading, close button and all), other stacks write it by hand, as here. Opened by commandfor / command="show-modal" on a button. The no-JS form comes first.
A link to the dialog's id shows it (:target); the close link # hides it. No script, no invoker commands.
The shape Alder emits for dismissible: the close form first, the heading <h2 slot="header"> with an id the dialog's aria-labelledby names, then a slot="description" line in muted text.
A floating panel anchored to its invoker. placement picks the side, offset the gap, anchor another anchor by id; the browser flips it when there is no room.
popovertarget and the popover attribute are the platform's: no script runs. (Only the placement needs a browser with CSS anchor positioning.)
popovertarget. Escape or a click outside closes it.A heading inside the popover is its title and a paragraph its description.
Set the dimensions for the layer.
Tabs backed by a radio group: they work with no script; the script builds the ARIA tabs pattern from them: a tablist of button role=tab (aria-selected, aria-controls, a roving tabindex) before the panels, the labels hidden, tabpanel with aria-labelledby. al-tab holds a label with a radio and its al-panel.
The radio group selects the tab and the arrow keys move between tabs; the server writes checked on the radio it picks.
With variant="line" the checked label is underlined; without the script there is no muted pill (the labels and panels share one flex row).
With the script: the ARIA roles and states, Home and End, selected picks the open tab (here the second), and a name on the tab supplies the radio's group.
variant="line" drops the muted pill and underlines the active tab.
A transient message in a corner region. The script shows the region, sets role="status" and closes the toast after timeout.
The toast region is a hidden popover until the script shows it, so the server also renders the message in the page as an al-alert:
With the script, each region is shown when its toast connects. On demo/toast.html these are sticky (timeout="0") so you can see all four variants at all four corners; the default timeout is 5000 ms and the toast closes itself. Text or a strong before the first block (div, p, list) is the title and the blocks are the description; a one-line message, even with inline elements, is all title. A toast with no variant has no icon.
A toast slides in from its region's edge and, when it closes, slides back out before it is removed (not with reduced motion). This one closes after 3000 ms.
A text-only hint for the element named by for, shown on hover and focus.
The tooltip stays hidden; put the same text in a title on the trigger.
Hover or focus the button: the tooltip is anchored to it, and the button gains aria-describedby. Escape hides it.
Alder's developer bar, as a design-system element: a collapsed bar and an expanded tabbed panel, server-rendered, with monospace code.
Open, every section shows (no tab switching); collapsed, only the bar. The server picks with open.
SELECT id, title FROM posts WHERE published = 1 ORDER BY id DESC;
With the script, the buttons switch sections and toggle open. This one starts collapsed: click a tab.
SELECT id, title FROM posts; UPDATE posts SET title = ? WHERE id = ?;
{"page": "home"}A server that patches the panel's inner content need not render hidden or aria-selected: the script re-applies the selection. "Patch" below replaces the panel's content with markup that has neither; the chosen section stays the only one showing.