Enclave UI kit
Theme
Theme
Shape
Radius
Radius
Border
Border
Shadow
Shadow
Padding
Padding
Type
Family
Family
Size
Size
Palette

These twelve roles are the whole palette. Everything else in the file is mixed from them.

Sign in

The design system, rendered

Every part of ns.css shown rendered beside its source, so a component is checked in dark, in Thai, at 150% before it is used anywhere. Search it from the bar, or walk the index on the left. The theme panel beside the search box carries eleven palettes and five shape knobs, live; the page's only script does three things — the colour inputs, the clipboard, and hiding the rows of that index that do not match what you typed.

Tokens and themes

Colours are roles, never hues. One declaration per role carries both schemes through light-dark(); every hover, disabled and outline shade is color-mix()ed from a tone rather than hand-picked, so an organization's brand colour propagates without a second palette.

The palette

Switch the theme above and every one of these moves together.

--bg
--surface
--ink
--muted
--outline
--primary
--secondary
--cta
--highlight
--danger
--success
--warn
Source
<div class="ns-grid ns-gap-3">
<div class="ns-span-3">
<div class="ns-stack ns-gap-1">
<div style="background:var(--bg);block-size:48px;border:1px solid var(--outline);border-radius:var(--radius)">
</div>
<small class="ns-mono">--bg</small>
</div>
</div>
<div class="ns-span-3">
<div class="ns-stack ns-gap-1">
<div style="background:var(--surface);block-size:48px;border:1px solid var(--outline);border-radius:var(--radius)">
</div>
<small class="ns-mono">--surface</small>
</div>
</div>
<div class="ns-span-3">
<div class="ns-stack ns-gap-1">
<div style="background:var(--ink);block-size:48px;border:1px solid var(--outline);border-radius:var(--radius)">
</div>
<small class="ns-mono">--ink</small>
</div>
</div>
<div class="ns-span-3">
<div class="ns-stack ns-gap-1">
<div style="background:var(--muted);block-size:48px;border:1px solid var(--outline);border-radius:var(--radius)">
</div>
<small class="ns-mono">--muted</small>
</div>
</div>
<div class="ns-span-3">
<div class="ns-stack ns-gap-1">
<div style="background:var(--outline);block-size:48px;border:1px solid var(--outline);border-radius:var(--radius)">
</div>
<small class="ns-mono">--outline</small>
</div>
</div>
<div class="ns-span-3">
<div class="ns-stack ns-gap-1">
<div style="background:var(--primary);block-size:48px;border:1px solid var(--outline);border-radius:var(--radius)">
</div>
<small class="ns-mono">--primary</small>
</div>
</div>
<div class="ns-span-3">
<div class="ns-stack ns-gap-1">
<div style="background:var(--secondary);block-size:48px;border:1px solid var(--outline);border-radius:var(--radius)">
</div>
<small class="ns-mono">--secondary</small>
</div>
</div>
<div class="ns-span-3">
<div class="ns-stack ns-gap-1">
<div style="background:var(--cta);block-size:48px;border:1px solid var(--outline);border-radius:var(--radius)">
</div>
<small class="ns-mono">--cta</small>
</div>
</div>
<div class="ns-span-3">
<div class="ns-stack ns-gap-1">
<div style="background:var(--highlight);block-size:48px;border:1px solid var(--outline);border-radius:var(--radius)">
</div>
<small class="ns-mono">--highlight</small>
</div>
</div>
<div class="ns-span-3">
<div class="ns-stack ns-gap-1">
<div style="background:var(--danger);block-size:48px;border:1px solid var(--outline);border-radius:var(--radius)">
</div>
<small class="ns-mono">--danger</small>
</div>
</div>
<div class="ns-span-3">
<div class="ns-stack ns-gap-1">
<div style="background:var(--success);block-size:48px;border:1px solid var(--outline);border-radius:var(--radius)">
</div>
<small class="ns-mono">--success</small>
</div>
</div>
<div class="ns-span-3">
<div class="ns-stack ns-gap-1">
<div style="background:var(--warn);block-size:48px;border:1px solid var(--outline);border-radius:var(--radius)">
</div>
<small class="ns-mono">--warn</small>
</div>
</div>
</div>
The spacing scale

Eight steps. There is no ninth; if a gap needs one, the component is wrong.

s-1
s-2
s-3
s-4
s-5
s-6
s-7
s-8
Source
<div class="ns-row ns-gap-2">
<div class="ns-stack ns-gap-1">
<div style="background:var(--primary);inline-size:var(--s-1);block-size:24px">
</div>
<small class="ns-mono">s-1</small>
</div>
<div class="ns-stack ns-gap-1">
<div style="background:var(--primary);inline-size:var(--s-2);block-size:24px">
</div>
<small class="ns-mono">s-2</small>
</div>
<div class="ns-stack ns-gap-1">
<div style="background:var(--primary);inline-size:var(--s-3);block-size:24px">
</div>
<small class="ns-mono">s-3</small>
</div>
<div class="ns-stack ns-gap-1">
<div style="background:var(--primary);inline-size:var(--s-4);block-size:24px">
</div>
<small class="ns-mono">s-4</small>
</div>
<div class="ns-stack ns-gap-1">
<div style="background:var(--primary);inline-size:var(--s-5);block-size:24px">
</div>
<small class="ns-mono">s-5</small>
</div>
<div class="ns-stack ns-gap-1">
<div style="background:var(--primary);inline-size:var(--s-6);block-size:24px">
</div>
<small class="ns-mono">s-6</small>
</div>
<div class="ns-stack ns-gap-1">
<div style="background:var(--primary);inline-size:var(--s-7);block-size:24px">
</div>
<small class="ns-mono">s-7</small>
</div>
<div class="ns-stack ns-gap-1">
<div style="background:var(--primary);inline-size:var(--s-8);block-size:24px">
</div>
<small class="ns-mono">s-8</small>
</div>
</div>

The grid

Bootstrap's twelve columns at 960px, measured against the CONTAINER rather than the viewport — a page may be a floating window 700px wide on a 2,000px screen, and the viewport is the wrong question to ask about it. Everything spans twelve below 640px of container, so a phone and a narrow window need no second set of classes. Narrow your window to watch these collapse.

Twelve columns

span-1

span-1

span-1

span-1

span-1

span-1

span-1

span-1

span-1

span-1

span-1

span-1

Source
<div class="ns-grid">
<div class="ns-span-1">
<p class="ns-card ns-pad-2 ns-small">span-1</p>
</div>
<div class="ns-span-1">
<p class="ns-card ns-pad-2 ns-small">span-1</p>
</div>
<div class="ns-span-1">
<p class="ns-card ns-pad-2 ns-small">span-1</p>
</div>
<div class="ns-span-1">
<p class="ns-card ns-pad-2 ns-small">span-1</p>
</div>
<div class="ns-span-1">
<p class="ns-card ns-pad-2 ns-small">span-1</p>
</div>
<div class="ns-span-1">
<p class="ns-card ns-pad-2 ns-small">span-1</p>
</div>
<div class="ns-span-1">
<p class="ns-card ns-pad-2 ns-small">span-1</p>
</div>
<div class="ns-span-1">
<p class="ns-card ns-pad-2 ns-small">span-1</p>
</div>
<div class="ns-span-1">
<p class="ns-card ns-pad-2 ns-small">span-1</p>
</div>
<div class="ns-span-1">
<p class="ns-card ns-pad-2 ns-small">span-1</p>
</div>
<div class="ns-span-1">
<p class="ns-card ns-pad-2 ns-small">span-1</p>
</div>
<div class="ns-span-1">
<p class="ns-card ns-pad-2 ns-small">span-1</p>
</div>
</div>
Halves and thirds

span-6

span-6

span-4

span-4

span-4

Source
<div class="ns-grid">
<div class="ns-span-6">
<p class="ns-card ns-pad-2 ns-small">span-6</p>
</div>
<div class="ns-span-6">
<p class="ns-card ns-pad-2 ns-small">span-6</p>
</div>
<div class="ns-span-4">
<p class="ns-card ns-pad-2 ns-small">span-4</p>
</div>
<div class="ns-span-4">
<p class="ns-card ns-pad-2 ns-small">span-4</p>
</div>
<div class="ns-span-4">
<p class="ns-card ns-pad-2 ns-small">span-4</p>
</div>
</div>
A bar's split

The header is a grid with a 3/6/3 split and no wrapper div.

span-3

span-6

span-3

Source
<div class="ns-grid">
<div class="ns-span-3">
<p class="ns-card ns-pad-2 ns-small">span-3</p>
</div>
<div class="ns-span-6">
<p class="ns-card ns-pad-2 ns-small">span-6</p>
</div>
<div class="ns-span-3">
<p class="ns-card ns-pad-2 ns-small">span-3</p>
</div>
</div>
Starting column

ns-start-N places a column without empty filler elements.

span-4 start-5

Source
<div class="ns-grid">
<div class="ns-span-4 ns-start-5">
<p class="ns-card ns-pad-2 ns-small">span-4 start-5</p>
</div>
</div>

Tones inherit

This is the mechanism the whole system rests on. A tone class sets two inherited custom properties; descendants read them through style queries. One class on the fieldset below colours the button, the badge and the callout inside it — none of them carries a class of its own.

One class, three children

Look at the source: only the fieldset is classed.

Danger zone

Deleting an organization cannot be undone.

Irreversible

Source
<fieldset class="ns-danger">
<legend>Danger zone</legend>
<p>Deleting an organization cannot be undone.</p>
<p>
<span class="ns-badge">Irreversible</span>
</p>
<aside class="ns-callout" role="note">This also deletes the tool database.</aside>
<p>
<button>Delete this organization</button>
</p>
</fieldset>
The tones
Source
<div class="ns-row ns-gap-2">
<button class="ns-primary">primary</button>
<button class="ns-secondary">secondary</button>
<button class="ns-cta">cta</button>
<button class="ns-danger">danger</button>
<button class="ns-success">success</button>
<button class="ns-warn">warn</button>
<button>no tone</button>
</div>
Shapes on a tone
Source
<div class="ns-row ns-gap-2 ns-primary">
<button>solid</button>
<button class="ns-outline">outline</button>
<button class="ns-ghost">ghost</button>
<button class="ns-pill">pill</button>
<button class="ns-sm">small</button>
<button class="ns-lg">large</button>
<button disabled>disabled</button>
</div>

Flow and space

Six flow classes and three spacing scales that set a custom property descendants consume. Per-side spacing is deliberately absent: a component owns its own insides, and ns-pl-7-style classes are how a closed set stops being one.

Row, wrapped and aligned
Members
Source
<div class="ns-row ns-between ns-middle">
<strong>Members</strong>
<button class="ns-sm ns-primary">Invite</button>
</div>
Stack

One

Two

Three

Source
<div class="ns-stack ns-gap-2">
<p class="ns-card ns-pad-2">One</p>
<p class="ns-card ns-pad-2">Two</p>
<p class="ns-card ns-pad-2">Three</p>
</div>
Density inherits too

ns-dense sets --dense, which the card and table blocks query to tighten their own padding.

Dense card

The card queried the density it was put inside.

Source
<div class="ns-dense">
<article class="ns-card">
<h3>Dense card</h3>
<p>The card queried the density it was put inside.</p>
</article>
</div>

Text and visibility

Seven text classes and three visibility ones. ns-sr is for text that must be announced but not seen; ns-print for the reverse.

Text

small

bold

mono 0123456789

right

truncated when it will not fit on one line at all

Source
<p class="ns-small">small</p>
<p class="ns-bold">bold</p>
<p class="ns-mono">mono 0123456789</p>
<p class="ns-right">right</p>
<p class="ns-truncate" style="max-inline-size:20ch">truncated when it will not fit on one line at all</p>
Announced but unseen

There is a heading before this paragraph that only a screen reader gets.

Section heading for assistive technology

The heading above is in the accessibility tree and not on the screen.

Source
<h4 class="ns-sr">Section heading for assistive technology</h4>
<p>The heading above is in the accessibility tree and not on the screen.</p>
Print only

Visible only when the page is printed — an invoice's legal footer.

This line appears only on paper.

(the line above is hidden on screen)

Source
<p class="ns-print">This line appears only on paper.</p>
<p class="ns-muted ns-small">(the line above is hidden on screen)</p>
Keys and code

The brief lists kbd beside code and samp, and for a long time the kit drew the other two and not this one. A key is not a code span: one is what you press, the other is what you type.

Press Ctrl + K to search, or type /ui in the address bar. The server answered 200 OK.

Source
<p>Press <kbd>Ctrl</kbd> + <kbd>K</kbd> to search, or type <code>/ui</code> in the address bar. The server answered <samp>200 OK</samp>.</p>
Prose

The one context the reset is wrong for. Every list in the system is furniture — a menu, a nav, a board, a thread — so markers are off by default; a rendered document is the exception and asks for them back.

What changed

Rendered Markdown lands here, from the changelog, the roadmap and the two legal texts. Everything it needs beyond the markers — headings, code, quotes, rules, tables and the spacing between blocks — is already in the base layer.

  • A list reads as a list. With a marker, and space between items.
    • And a nested one indents.
  • A link takes the tone it is in. Like this one.

A quote keeps its rule and its indent.

Source
<div class="ns-prose">
<h3>What changed</h3>
<p>Rendered Markdown lands here, from the changelog, the roadmap and the two legal texts. Everything it needs beyond the markers — headings, <code>code</code>, quotes, rules, tables and the spacing between blocks — is already in the base layer.</p>
<ul>
<li>
<strong>A list reads as a list.</strong> With a marker, and space between items.<ul>
<li>And a nested one indents.</li>
</ul>
</li>
<li>
<strong>A link takes the tone it is in.</strong> Like <a href="#text-prose">this one</a>.</li>
</ul>
<blockquote>
<p>A quote keeps its rule and its indent.</p>
</blockquote>
</div>

Cards, callouts and badges

An article is a card, an aside is a callout, and a span is a badge. The element carries the meaning; the class only says how it is drawn.

Card
Summer terrace

Refit of the back terrace before June.

Source
<article class="ns-card">
<header>Summer terrace</header>
<p>Refit of the back terrace before June.</p>
<footer>
<span class="ns-badge ns-success">On track</span>
<a role="button" class="ns-sm" href="#card">Open</a>
</footer>
</article>
Callouts
Source
<aside class="ns-callout" role="note">A plain note.</aside>
<aside class="ns-callout ns-warn" role="note">Your storage is nearly full.</aside>
<aside class="ns-callout ns-danger" role="note">This organization is frozen.</aside>
Badges

DraftPaidOverdueVoid12

Source
<p class="ns-row ns-gap-2">
<span class="ns-badge">Draft</span>
<span class="ns-badge ns-success">Paid</span>
<span class="ns-badge ns-warn">Overdue</span>
<span class="ns-badge ns-danger">Void</span>
<data class="ns-badge" value="12">12</data>
</p>

People

An avatar is a button when it opens a menu and a link when it navigates. data-status draws the presence dot; the verified check is drawn by one Blade component and never by a view.

Avatars

DAMPPREZAB

Source
<p class="ns-row ns-gap-2">
<span class="ns-avatar" data-status="online">
<span>DA</span>
</span>
<span class="ns-avatar" data-status="away">
<span>MP</span>
</span>
<span class="ns-avatar" data-status="offline">
<span>PR</span>
</span>
<span class="ns-avatar ns-lg">
<span>EZ</span>
</span>
<span class="ns-avatar ns-sm">
<span>AB</span>
</span>
</p>
A person

What <x-person> writes, and the ONE place the verified check is drawn. A badge rendered from several places is one that will eventually appear beside somebody who has not earned it — and a verification mark that is sometimes wrong is worse than none, because people believe it.

AOAda OkonjoVerifiedBLBo Lindqvist?Someone
Source
<div class="ns-stack ns-gap-3">
<span class="ns-row ns-gap-2 ns-middle">
<span class="ns-avatar">AO</span>
<span class="ns-row ns-gap-1 ns-middle">
<a href="#people-a-person">Ada Okonjo</a>
<svg class="ns-icon ns-sm ns-primary" role="img" aria-label="Verified">
<title>Verified</title>
<use href="#i-check">
</use>
</svg>
</span>
</span>
<span class="ns-row ns-gap-2 ns-middle">
<span class="ns-avatar">BL</span>
<span class="ns-row ns-gap-1 ns-middle">
<a href="#people-a-person">Bo Lindqvist</a>
</span>
</span>
<span class="ns-row ns-gap-2 ns-middle">
<span class="ns-avatar ns-sm">?</span>
<span class="ns-muted">Someone</span>
</span>
</div>

Tables

A table with a caption, sortable headers as links, and data-kind on numeric cells so they align right and share one figure width. It scrolls inside its own figure; the page body never scrolls sideways.

Invoices
Invoices, October 2026
NumberClientIssuedTotal
0007Bluebird1,240.00
0006Marcos SA380.50
0005Bluebird12,000.00
Source
<figure>
<table>
<caption>Invoices, October 2026</caption>
<thead>
<tr>
<th>
<a href="#table">Number</a>
</th>
<th>
<a href="#table">Client</a>
</th>
<th aria-sort="descending">
<a href="#table">Issued</a>
</th>
<th data-kind="money">
<a href="#table">Total</a>
</th>
</tr>
</thead>
<tbody>
<tr>
<td>0007</td>
<td>Bluebird</td>
<td>
<time datetime="2026-10-12">12 Oct</time>
</td>
<td data-kind="money">1,240.00</td>
</tr>
<tr>
<td>0006</td>
<td>Marcos SA</td>
<td>
<time datetime="2026-10-04">4 Oct</time>
</td>
<td data-kind="money">380.50</td>
</tr>
<tr>
<td>0005</td>
<td>Bluebird</td>
<td>
<time datetime="2026-09-28">28 Sep</time>
</td>
<td data-kind="money">12,000.00</td>
</tr>
</tbody>
</table>
</figure>
Pagination
Source
<nav aria-label="Pages">
<a rel="prev" href="#table">Previous</a>
<span class="ns-muted ns-small">Page 2 of 9</span>
<a rel="next" href="#table">Next</a>
</nav>

Accordion, menu and dialog

Three things that would each be a script in another stack. details[name] makes an accordion exclusive natively; popover opens and closes itself and is anchored in CSS; dialog with form[method=dialog] closes with no JavaScript at all.

Accordion

The name attribute makes them mutually exclusive — no script.

What happens when I hit my member limit?

Invitations stop; nobody is removed and nothing is deleted.

Can I bring my own database?

Yes. We provision one so tools work on day one, and you can replace it.

What does closing an organization delete?

Everything, after fourteen days frozen and an export you have to decline.

Source
<details name="faq">
<summary>What happens when I hit my member limit?</summary>
<p>Invitations stop; nobody is removed and nothing is deleted.</p>
</details>
<details name="faq">
<summary>Can I bring my own database?</summary>
<p>Yes. We provision one so tools work on day one, and you can replace it.</p>
</details>
<details name="faq">
<summary>What does closing an organization delete?</summary>
<p>Everything, after fourteen days frozen and an export you have to decline.</p>
</details>
Menu

A button with popovertarget and a menu with popover. Anchored in CSS.

  • Rename
  • Duplicate

  • Source
    <button popovertarget="ui-menu">Actions</button>
    <menu popover id="ui-menu" class="ns-menu">
    <li>
    <a href="#disclosure">Rename</a>
    </li>
    <li>
    <a href="#disclosure">Duplicate</a>
    </li>
    <li>
    <hr>
    </li>
    <li>
    <form>
    <button class="ns-danger ns-ghost">Delete</button>
    </form>
    </li>
    </menu>
    The error dialog

    What a 403 or 404 renders. Both codes say the same sentence, because the difference between them is exactly what per-object hiding conceals.

    That page does not exist, or you cannot open it.

    /acme/projects/7c9e2b1a

    Source
    <dialog open role="alertdialog" style="position:static;display:block">
    <h3>That page does not exist, or you cannot open it.</h3>
    <p class="ns-muted ns-small ns-mono">/acme/projects/7c9e2b1a</p>
    <form method="dialog">
    <button>Close</button>
    </form>
    </dialog>
    Flashed message

    There is no toast, because there is no script to show one.

    Invitation sent to dana@example.com.
    Source
    <output role="status">Invitation sent to dana@example.com.</output>

    Empty states and progress

    An empty state is one sentence and one link, never an illustration and a paragraph. A meter is the disk usage; a progress is work underway.

    Empty

    No projects yet. Create the first one

    Source
    <p class="ns-empty">No projects yet. <a href="#empty">Create the first one</a>
    </p>
    Meter and progress

    1.4 GB of 2 GB

    60%

    Source
    <p>
    <label for="ui-disk">Storage</label> <meter id="ui-disk" value="1.4" max="2" low="1.2" high="1.8">
    </meter> <small>1.4 GB of 2 GB</small>
    </p>
    <p>
    <label for="ui-exp">Export</label> <progress id="ui-exp" value="0.6">
    </progress> <small>60%</small>
    </p>
    Slider of cards

    CSS scroll-snap, with the browser drawing the dots where it supports scroll markers. No script.

    Marketing funnel

    Leads, stages, a board and a public form.

    Customer relationship manager

    Companies, contacts, deals and a map.

    SEO tools

    Sites, pages and issues, crawled by a companion script.

    Uptime monitor

    Monitors and checks, with the last day as a meter.

    Electronic timekeeping

    Incidences, holidays and a sealed pay period.

    Source
    <div class="ns-slider">
    <article class="ns-card">
    <h3>Marketing funnel</h3>
    <p>Leads, stages, a board and a public form.</p>
    </article>
    <article class="ns-card">
    <h3>Customer relationship manager</h3>
    <p>Companies, contacts, deals and a map.</p>
    </article>
    <article class="ns-card">
    <h3>SEO tools</h3>
    <p>Sites, pages and issues, crawled by a companion script.</p>
    </article>
    <article class="ns-card">
    <h3>Uptime monitor</h3>
    <p>Monitors and checks, with the last day as a meter.</p>
    </article>
    <article class="ns-card">
    <h3>Electronic timekeeping</h3>
    <p>Incidences, holidays and a sealed pay period.</p>
    </article>
    </div>

    Field types

    One component, one attribute. The type chooses the control, and a type not in this table is a Form Request rule plus one of these — never a new control. Every picker is the browser's own: the date picker is the date picker.

    type="text"
    Shown to everyone in the organization.
    Source
    <form>
    <label for="f-text">Organization name</label>
    <input id="f-text" name="name" type="text" autocomplete="organization">
    <small id="f-text-hint">Shown to everyone in the organization.</small>
    </form>
    type="email"
    Source
    <form>
    <label for="f-email">Sign-in email</label>
    <input id="f-email" name="email" type="email" inputmode="email" autocomplete="email">
    </form>
    type="url"
    Source
    <form>
    <label for="f-url">Website</label>
    <input id="f-url" name="url" type="url" inputmode="url" placeholder="https://">
    </form>
    type="tel"
    Source
    <form>
    <label for="f-tel">Phone</label>
    <input id="f-tel" name="tel" type="tel" inputmode="tel" autocomplete="tel">
    </form>
    type="search"
    Source
    <form>
    <label for="f-search">Search</label>
    <input id="f-search" name="q" type="search" list="f-recent">
    <datalist id="f-recent">
    <option value="allergen sheet">
    </option>
    <option value="summer terrace">
    </option>
    </datalist>
    </form>
    type="textarea"
    Source
    <form>
    <label for="f-ta">Bio</label>
    <textarea id="f-ta" name="bio" rows="3">This textarea grows with its content.</textarea>
    </form>
    type="markdown"
    Formatting

    **bold** _italic_ [[note]] @member #project

    Source
    <form>
    <label for="f-md">Note</label>
    <textarea id="f-md" name="body" rows="3">The **editor** is a textarea. The server renders the result below it.</textarea>
    <details>
    <summary>Formatting</summary>
    <p class="ns-small ns-mono">**bold** _italic_ [[note]] @member #project</p>
    </details>
    </form>
    type="number"
    Source
    <form>
    <label for="f-num">Seats</label>
    <input id="f-num" name="seats" type="number" inputmode="decimal" min="1" max="30" step="1" value="10">
    </form>
    type="money"
    Source
    <form>
    <label for="f-money">Amount</label>
    <span class="ns-row ns-gap-2">
    <input id="f-money" name="amount" inputmode="decimal" value="1,240.00">
    <select name="currency" aria-label="Currency">
    <option>MXN</option>
    <option selected>USD</option>
    <option>EUR</option>
    </select>
    </span>
    </form>
    type="quantity"
    Source
    <form>
    <label for="f-qty">Quantity</label>
    <input id="f-qty" name="qty" type="number" step="0.001" value="1.500">
    </form>
    type="boolean"
    Source
    <form>
    <label>
    <input type="checkbox" name="pinned" checked> Pin this post</label>
    </form>
    type="toggle"
    Source
    <form>
    <label>
    <input type="checkbox" role="switch" class="ns-switch" name="mfa" checked> Require two-factor</label>
    </form>
    type="checkboxes"
    Notify me about
    Source
    <form>
    <fieldset>
    <legend>Notify me about</legend>
    <label>
    <input type="checkbox" checked> Mentions</label>
    <label>
    <input type="checkbox" checked> Task assignments</label>
    <label>
    <input type="checkbox"> Pinned posts</label>
    </fieldset>
    </form>
    type="radio"
    Backup schedule
    Source
    <form>
    <fieldset>
    <legend>Backup schedule</legend>
    <label>
    <input type="radio" name="sched"> Off</label>
    <label>
    <input type="radio" name="sched" checked> Weekly</label>
    <label>
    <input type="radio" name="sched"> Daily</label>
    </fieldset>
    </form>
    type="segmented"
    Billing period
    Source
    <form>
    <fieldset class="ns-segmented">
    <legend class="ns-sr">Billing period</legend>
    <label>
    <input type="radio" name="period" checked> Monthly</label>
    <label>
    <input type="radio" name="period"> Yearly</label>
    </fieldset>
    </form>
    type="pills"
    Teams
    Source
    <form>
    <fieldset class="ns-pills">
    <legend class="ns-sr">Teams</legend>
    <label>
    <input type="checkbox" checked> Kitchen</label>
    <label>
    <input type="checkbox"> Front of house</label>
    <label>
    <input type="checkbox" checked> Management</label>
    </fieldset>
    </form>
    type="select"
    Source
    <form>
    <label for="f-sel">Role</label>
    <select id="f-sel" name="role">
    <option>Owner</option>
    <option selected>Admin</option>
    <option>Manager</option>
    <option>Member</option>
    <option>Auditor</option>
    <option>Client</option>
    </select>
    </form>
    type="autocomplete"
    Source
    <form>
    <label for="f-ac">Project</label>
    <input id="f-ac" name="project" list="f-projects">
    <datalist id="f-projects">
    <option value="Summer terrace">
    </option>
    <option value="Allergen review">
    </option>
    </datalist>
    </form>
    type="reference"
    Posts the UUID, not the name.
    Source
    <form>
    <label for="f-ref">Assignee</label>
    <input id="f-ref" name="assignee" list="f-people">
    <datalist id="f-people">
    <option value="Dana">
    </option>
    <option value="Marco">
    </option>
    </datalist>
    <small>Posts the UUID, not the name.</small>
    </form>
    type="address"
    Validated by Address::parse.
    Source
    <form>
    <label for="f-addr">Address</label>
    <input id="f-addr" name="address" value="/acme/files/contracts">
    <small>Validated by Address::parse.</small>
    </form>
    type="place"
    Source
    <form>
    <label for="f-place">Place</label>
    <input id="f-place" name="place" list="f-places">
    <datalist id="f-places">
    <option value="Kitchen door">
    </option>
    <option value="Warehouse">
    </option>
    </datalist>
    </form>
    type="coordinates"
    Coordinates
    Source
    <form>
    <fieldset>
    <legend>Coordinates</legend>
    <span class="ns-row ns-gap-2">
    <input aria-label="Latitude" type="number" step="0.000001" value="19.432608">
    <input aria-label="Longitude" type="number" step="0.000001" value="-99.133209">
    </span>
    </fieldset>
    </form>
    type="range"
    120 m
    Source
    <form>
    <label for="f-range">Radius</label>
    <input id="f-range" type="range" min="0" max="500" value="120"> <output for="f-range">120 m</output>
    </form>
    type="slider"
    Source
    <form>
    <label for="f-slider">Members</label>
    <span class="ns-row ns-gap-2">
    <input id="f-slider" type="range" min="1" max="30" value="10" list="f-ticks">
    <input type="number" aria-label="Exact value" min="1" max="30" value="10">
    </span>
    <datalist id="f-ticks">
    <option value="1">
    </option>
    <option value="10">
    </option>
    <option value="30">
    </option>
    </datalist>
    </form>
    type="color"
    Presets
    Source
    <form>
    <label for="f-color">Brand colour</label>
    <input id="f-color" type="color" value="#1f6b5a">
    <fieldset class="ns-pills">
    <legend class="ns-sr">Presets</legend>
    <label>
    <input type="radio" name="preset" checked> Green</label>
    <label>
    <input type="radio" name="preset"> Slate</label>
    </fieldset>
    </form>
    type="date"
    Source
    <form>
    <label for="f-date">Due</label>
    <input id="f-date" type="date" value="2026-10-12">
    </form>
    type="time"
    Source
    <form>
    <label for="f-time">Opens</label>
    <input id="f-time" type="time" value="09:00">
    </form>
    type="datetime"
    Source
    <form>
    <label for="f-dt">Starts</label>
    <input id="f-dt" type="datetime-local" value="2026-10-12T09:00">
    </form>
    type="month"
    Source
    <form>
    <label for="f-month">Period</label>
    <input id="f-month" type="month" value="2026-10">
    </form>
    type="week"
    Source
    <form>
    <label for="f-week">Week</label>
    <input id="f-week" type="week" value="2026-W41">
    </form>
    type="duration"
    Break
    Source
    <form>
    <fieldset>
    <legend>Break</legend>
    <span class="ns-row ns-gap-2">
    <input type="number" aria-label="Hours" min="0" value="0">
    <input type="number" aria-label="Minutes" min="0" max="59" value="30">
    </span>
    </fieldset>
    </form>
    type="picture"
    Source
    <form>
    <label for="f-pic">Photo</label>
    <input id="f-pic" type="file" accept="image/*" capture="environment">
    </form>
    type="file"
    Source
    <form>
    <label for="f-file">Attachment</label>
    <input id="f-file" type="file">
    </form>
    type="files"
    1.4 GB of 2 GB
    Source
    <form>
    <label for="f-files">Attachments</label>
    <input id="f-files" type="file" multiple>
    <meter value="0.7" max="2">
    </meter> <small>1.4 GB of 2 GB</small>
    </form>
    type="otp"
    Source
    <form>
    <label for="f-otp">Six-digit code</label>
    <input id="f-otp" inputmode="numeric" autocomplete="one-time-code" pattern="[0-9]{6}" maxlength="6">
    </form>
    type="rating"
    Rating
    Source
    <form>
    <fieldset class="ns-segmented">
    <legend class="ns-sr">Rating</legend>
    <label>
    <input type="radio" name="rate"> 1</label>
    <label>
    <input type="radio" name="rate"> 2</label>
    <label>
    <input type="radio" name="rate"> 3</label>
    <label>
    <input type="radio" name="rate" checked> 4</label>
    <label>
    <input type="radio" name="rate"> 5</label>
    </fieldset>
    </form>
    type="timezone"
    Source
    <form>
    <label for="f-tz">Time zone</label>
    <select id="f-tz">
    <optgroup label="America">
    <option selected>America/Mexico_City</option>
    <option>America/New_York</option>
    </optgroup>
    <optgroup label="Asia">
    <option>Asia/Bangkok</option>
    </optgroup>
    </select>
    </form>
    type="country"
    Source
    <form>
    <label for="f-country">Country</label>
    <select id="f-country">
    <option selected>Mexico (+52)</option>
    <option>Thailand (+66)</option>
    <option>Spain (+34)</option>
    </select>
    </form>
    type="locale"
    Source
    <form>
    <label for="f-locale">Language</label>
    <select id="f-locale">
    <option selected>English</option>
    <option>Espa&#241;ol</option>
    <option disabled>&#20013;&#25991; (coming soon)</option>
    <option disabled>&#3652;&#3607;&#3618; (coming soon)</option>
    </select>
    </form>
    type="currency"
    Source
    <form>
    <label for="f-cur">Currency</label>
    <select id="f-cur">
    <option>MXN</option>
    <option selected>USD</option>
    </select>
    </form>
    type="hidden"

    A hidden input renders nothing; it is here so the registry test can see the type exists.

    Source
    <form>
    <input type="hidden" name="organization" value="acme">
    <p class="ns-muted ns-small">A hidden input renders nothing; it is here so the registry test can see the type exists.</p>
    </form>

    Search, menus and sticking

    A dropdown is the popover attribute and nothing else: the browser opens it, closes it on Esc and on a click outside, anchors it to its own trigger and puts focus back. A search box is an input with a type. Neither costs a line of script.

    Dropdown

    Two dropdowns on one page, to prove they anchor to their own buttons. A shared anchor name would send both to whichever trigger came last.

    Source
    <div class="ns-row ns-gap-3">
    <div>
    <button popovertarget="m-file">File<svg class="ns-icon ns-sm" aria-hidden="true">
    <use href="#i-chevron-down">
    </use>
    </svg>
    </button>
    <ul popover id="m-file" class="ns-menu">
    <li>
    <a href="#controls-dropdown">
    <svg class="ns-icon" aria-hidden="true">
    <use href="#i-file-text">
    </use>
    </svg>New document</a>
    </li>
    <li>
    <a href="#controls-dropdown">
    <svg class="ns-icon" aria-hidden="true">
    <use href="#i-folder">
    </use>
    </svg>New folder</a>
    </li>
    <li>
    <hr>
    </li>
    <li>
    <a href="#controls-dropdown">
    <svg class="ns-icon" aria-hidden="true">
    <use href="#i-download">
    </use>
    </svg>Export</a>
    </li>
    </ul>
    </div>
    <div>
    <button popovertarget="m-acct">Account<svg class="ns-icon ns-sm" aria-hidden="true">
    <use href="#i-chevron-down">
    </use>
    </svg>
    </button>
    <ul popover id="m-acct" class="ns-menu">
    <li>
    <a href="#controls-dropdown">
    <svg class="ns-icon" aria-hidden="true">
    <use href="#i-user">
    </use>
    </svg>Profile</a>
    </li>
    <li>
    <a href="#controls-dropdown">
    <svg class="ns-icon" aria-hidden="true">
    <use href="#i-settings">
    </use>
    </svg>Settings</a>
    </li>
    <li>
    <hr>
    </li>
    <li>
    <a href="#controls-dropdown">
    <svg class="ns-icon" aria-hidden="true">
    <use href="#i-log-out">
    </use>
    </svg>Sign out</a>
    </li>
    </ul>
    </div>
    </div>
    A head that stays

    ns-sticky stops below the bar rather than under it, because the bar is sticky too. Scroll the page with this in view.

    PersonTeamHours
    Ada OkonjoDesign31.5
    Bo LindqvistSupport28.0
    Chen WeiDesign36.25
    Dalia HaddadFinance22.0
    Source
    <table>
    <thead class="ns-sticky">
    <tr>
    <th>Person</th>
    <th>Team</th>
    <th class="ns-right">Hours</th>
    </tr>
    </thead>
    <tbody>
    <tr>
    <td>Ada Okonjo</td>
    <td>Design</td>
    <td class="ns-right">31.5</td>
    </tr>
    <tr>
    <td>Bo Lindqvist</td>
    <td>Support</td>
    <td class="ns-right">28.0</td>
    </tr>
    <tr>
    <td>Chen Wei</td>
    <td>Design</td>
    <td class="ns-right">36.25</td>
    </tr>
    <tr>
    <td>Dalia Haddad</td>
    <td>Finance</td>
    <td class="ns-right">22.0</td>
    </tr>
    </tbody>
    </table>

    Dialogs, drawers and tips

    Every overlay here is a <dialog>, opened by command="show-modal" on an ordinary button and closed by command="close" or a form. The browser supplies the backdrop, the Escape key, the focus trap, the inert page behind and the focus restored afterwards — all of which a hand-written modal gets wrong.

    A dialog

    No script. The button names the dialog and the command it wants; that is the whole mechanism.

    Rename team

    Everyone in the team sees the new name immediately.

    Source
    <div class="ns-row ns-gap-2">
    <button class="ns-primary" commandfor="d-edit" command="show-modal">Rename team</button>
    <dialog id="d-edit">
    <h3>Rename team</h3>
    <p class="ns-muted ns-small">Everyone in the team sees the new name immediately.</p>
    <label for="d-edit-name">Name</label>
    <input id="d-edit-name" type="text" value="Design">
    <form method="dialog" class="ns-m-4">
    <button value="cancel">Cancel</button>
    <button class="ns-primary" value="save">Save</button>
    </form>
    </dialog>
    </div>
    A refusal that needs confirming

    The danger tone is on the dialog, so the button inside it needs no class of its own.

    Delete “Q3 planning”?

    Nine documents and the whole message history go with it. This cannot be undone.

    Source
    <div class="ns-row ns-gap-2">
    <button class="ns-danger ns-outline" commandfor="d-del" command="show-modal">Delete workspace</button>
    <dialog id="d-del" class="ns-danger">
    <h3>Delete “Q3 planning”?</h3>
    <p>Nine documents and the whole message history go with it. This cannot be undone.</p>
    <form method="dialog" class="ns-m-4">
    <button value="cancel">Keep it</button>
    <button class="ns-danger" value="delete">Delete</button>
    </form>
    </dialog>
    </div>
    Drawers, four sides

    The same dialog element pinned to an edge with data-side. It slides in with @starting-style and slides out with allow-discrete, and neither needs a keyframe.

    Filters

    A left drawer is where a list keeps the controls it cannot fit.

    Details

    A right drawer is where a row shows the rest of itself.

    Search everything

    A top drawer is a command bar.

    Actions

    A bottom drawer is a phone's action sheet.

    Source
    <div class="ns-row ns-gap-2">
    <button commandfor="dr-l" command="show-modal">Left</button>
    <button commandfor="dr-r" command="show-modal">Right</button>
    <button commandfor="dr-t" command="show-modal">Top</button>
    <button commandfor="dr-b" command="show-modal">Bottom</button>
    <dialog id="dr-l" class="ns-drawer" data-side="left">
    <h3>Filters</h3>
    <p class="ns-muted ns-small">A left drawer is where a list keeps the controls it cannot fit.</p>
    <form method="dialog">
    <button>Close</button>
    </form>
    </dialog>
    <dialog id="dr-r" class="ns-drawer" data-side="right">
    <h3>Details</h3>
    <p class="ns-muted ns-small">A right drawer is where a row shows the rest of itself.</p>
    <form method="dialog">
    <button>Close</button>
    </form>
    </dialog>
    <dialog id="dr-t" class="ns-drawer" data-side="top">
    <h3>Search everything</h3>
    <p class="ns-muted ns-small">A top drawer is a command bar.</p>
    <form method="dialog">
    <button>Close</button>
    </form>
    </dialog>
    <dialog id="dr-b" class="ns-drawer" data-side="bottom">
    <h3>Actions</h3>
    <p class="ns-muted ns-small">A bottom drawer is a phone's action sheet.</p>
    <form method="dialog">
    <button>Close</button>
    </form>
    </dialog>
    </div>
    Tooltips

    data-tip on anything, shown on hover AND on keyboard focus. It repeats a name that is already accessible; nothing a person must read before acting is ever only in a tip.

    Trial
    Source
    <div class="ns-row ns-gap-2">
    <button data-tip="Save the document">
    <svg class="ns-icon" aria-hidden="true">
    <use href="#i-save">
    </use>
    </svg>
    <span class="ns-sr">Save</span>
    </button>
    <button data-tip="Attach a file">
    <svg class="ns-icon" aria-hidden="true">
    <use href="#i-paperclip">
    </use>
    </svg>
    <span class="ns-sr">Attach</span>
    </button>
    <button class="ns-danger ns-ghost" data-tip="Delete for everyone" data-tip-side="bottom">
    <svg class="ns-icon" aria-hidden="true">
    <use href="#i-trash">
    </use>
    </svg>
    <span class="ns-sr">Delete</span>
    </button>
    <span class="ns-badge" data-tip="Renews on 4 October">Trial</span>
    </div>

    Progress and waiting

    The <progress> element, which already means this. It takes the tone of whatever it is inside, and with no value attribute it says the length is unknown by moving — the one honest thing a bar can do when it does not know.

    How far along
    Source
    <div class="ns-stack ns-gap-3">
    <label for="pr-1">Uploading four files</label>
    <progress id="pr-1" value="0.34">
    </progress>
    <div class="ns-success">
    <label for="pr-2">Migration</label>
    <progress id="pr-2" value="0.86">
    </progress>
    </div>
    <div class="ns-danger">
    <label for="pr-3">Storage used</label>
    <progress id="pr-3" value="0.97">
    </progress>
    </div>
    </div>
    A length nobody knows

    No value attribute. It stops moving under prefers-reduced-motion rather than being removed, so it still reads as busy.

    This runs on the ten-minute schedule and finishes when it finishes.

    Source
    <div class="ns-stack ns-gap-2">
    <label for="pr-4">Reindexing search</label>
    <progress id="pr-4">
    </progress>
    <p class="ns-small ns-muted">This runs on the ten-minute schedule and finishes when it finishes.</p>
    </div>
    Steps

    An ordered list is what a sequence of steps is, and aria-current says where you are.

    1. Account
    2. Organization
    3. Team
    4. Billing
    Source
    <ol class="ns-pills">
    <li>
    <span class="ns-badge ns-success">
    <svg class="ns-icon ns-sm" aria-hidden="true">
    <use href="#i-check">
    </use>
    </svg>Account</span>
    </li>
    <li>
    <span class="ns-badge ns-success">
    <svg class="ns-icon ns-sm" aria-hidden="true">
    <use href="#i-check">
    </use>
    </svg>Organization</span>
    </li>
    <li>
    <span class="ns-badge ns-primary" aria-current="step">Team</span>
    </li>
    <li>
    <span class="ns-badge ns-muted">Billing</span>
    </li>
    </ol>

    Charts

    Four shapes, no library, no canvas. The drawing is decoration and is marked as such; the DATA is a table underneath it, generated from the same numbers by <x-chart> so the two cannot disagree. That table is the reason a chart here is readable by somebody who cannot see it — for a while this blurb promised one and there was not one, which is worth knowing about any claim a design system makes about itself.

    Bars

    The whole shape, which is what <x-chart> writes for you: a figure, a caption, the drawing marked aria-hidden because it IS decoration, and the numbers as a table. The table is not a courtesy — it is where the data lives for anybody who cannot see the bars, and the drawing is generated from it so the two cannot disagree.

    Signups per day
    The numbers
    Signups per day
    DaySignups
    Mon42
    Tue61
    Wed55
    Thu88
    Fri73
    Sat21
    Sun14
    Source
    <figure class="ns-stack ns-gap-2">
    <figcaption class="ns-small ns-muted">Signups per day</figcaption>
    <div class="ns-chart ns-primary" data-chart="bar" aria-hidden="true">
    <div style="--v:.42" data-label="Mon" data-value="42">
    </div>
    <div style="--v:.61" data-label="Tue" data-value="61">
    </div>
    <div style="--v:.55" data-label="Wed" data-value="55">
    </div>
    <div style="--v:.88" data-label="Thu" data-value="88">
    </div>
    <div style="--v:.73" data-label="Fri" data-value="73">
    </div>
    <div style="--v:.21" data-label="Sat" data-value="21">
    </div>
    <div style="--v:.14" data-label="Sun" data-value="14">
    </div>
    </div>
    <details>
    <summary class="ns-small">The numbers</summary>
    <table>
    <caption class="ns-sr">Signups per day</caption>
    <thead>
    <tr>
    <th scope="col">Day</th>
    <th scope="col" class="ns-right">Signups</th>
    </tr>
    </thead>
    <tbody>
    <tr>
    <th scope="row">Mon</th>
    <td class="ns-right">
    <data value="42">42</data>
    </td>
    </tr>
    <tr>
    <th scope="row">Tue</th>
    <td class="ns-right">
    <data value="61">61</data>
    </td>
    </tr>
    <tr>
    <th scope="row">Wed</th>
    <td class="ns-right">
    <data value="55">55</data>
    </td>
    </tr>
    <tr>
    <th scope="row">Thu</th>
    <td class="ns-right">
    <data value="88">88</data>
    </td>
    </tr>
    <tr>
    <th scope="row">Fri</th>
    <td class="ns-right">
    <data value="73">73</data>
    </td>
    </tr>
    <tr>
    <th scope="row">Sat</th>
    <td class="ns-right">
    <data value="21">21</data>
    </td>
    </tr>
    <tr>
    <th scope="row">Sun</th>
    <td class="ns-right">
    <data value="14">14</data>
    </td>
    </tr>
    </tbody>
    </table>
    </details>
    </figure>
    Bars lying down

    When the labels are words rather than dates, turn the chart instead of the text.

    Source
    <div class="ns-chart ns-cta" data-chart="hbar">
    <div style="--v:.92" data-label="Design" data-value="92">
    <span>
    </span>
    </div>
    <div style="--v:.61" data-label="Support" data-value="61">
    <span>
    </span>
    </div>
    <div style="--v:.48" data-label="Finance" data-value="48">
    <span>
    </span>
    </div>
    <div style="--v:.23" data-label="Legal" data-value="23">
    <span>
    </span>
    </div>
    </div>
    A ring

    One conic-gradient and one radial mask. The slices are an inline style because they are the data.

    • Documents · 46%
    • Media · 25%
    • Messages · 17%
    • Free · 12%
    Source
    <div class="ns-row ns-gap-5 ns-middle">
    <div class="ns-chart" data-chart="donut" style="background:conic-gradient(var(--primary) 0 46%,var(--cta) 46% 71%,var(--success) 71% 88%,var(--outline) 88% 100%)">
    </div>
    <ul class="ns-stack">
    <li class="ns-primary">Documents · 46%</li>
    <li class="ns-cta">Media · 25%</li>
    <li class="ns-success">Messages · 17%</li>
    <li class="ns-muted">Free · 12%</li>
    </ul>
    </div>
    A line, and the area under it

    An inline SVG, because a polyline IS the series and nothing else states it as plainly. The stylesheet only colours it.

    Source
    <div class="ns-chart ns-primary" data-chart="line">
    <svg viewBox="0 0 300 100" preserveAspectRatio="none" role="img" aria-label="Signups per week, rising from 18 to 74">
    <path d="M0,82 50,74 100,58 150,63 200,40 250,26 300,14 300,100 0,100Z"/>
    <polyline points="0,82 50,74 100,58 150,63 200,40 250,26 300,14"/>
    <circle cx="300" cy="14" r="4"/>
    </svg>
    </div>

    Icons

    Fifty-nine Feather outlines inlined once per page as one sprite: no request, no build, no npm. They are strokes rather than fills, so an icon inherits the colour of the text it sits in and needs no variant per tone.

    In a button, where they belong

    An icon beside a word is a help. An icon alone needs a tip and a screen-reader name, and both are shown here.

    Needs review
    Source
    <div class="ns-row ns-gap-2">
    <button class="ns-primary">
    <svg class="ns-icon" aria-hidden="true">
    <use href="#i-plus">
    </use>
    </svg>New project</button>
    <button>
    <svg class="ns-icon" aria-hidden="true">
    <use href="#i-download">
    </use>
    </svg>Export</button>
    <button class="ns-ghost ns-square" data-tip="Settings">
    <svg class="ns-icon" aria-hidden="true">
    <use href="#i-settings">
    </use>
    </svg>
    <span class="ns-sr">Settings</span>
    </button>
    <span class="ns-badge ns-warn">
    <svg class="ns-icon ns-sm" aria-hidden="true">
    <use href="#i-alert-triangle">
    </use>
    </svg>Needs review</span>
    </div>
    The picker

    Radio buttons in a grid. The selection is document state, so the back button restores it and no script is involved.

    Icon for this project
    Source
    <fieldset class="ns-pills">
    <legend class="ns-small ns-muted">Icon for this project</legend>
    <label data-tip="home">
    <input type="radio" name="pick">
    <svg class="ns-icon" aria-hidden="true">
    <use href="#i-home">
    </use>
    </svg>
    </label>
    <label data-tip="users">
    <input type="radio" name="pick">
    <svg class="ns-icon" aria-hidden="true">
    <use href="#i-users">
    </use>
    </svg>
    </label>
    <label data-tip="calendar">
    <input type="radio" name="pick" checked>
    <svg class="ns-icon" aria-hidden="true">
    <use href="#i-calendar">
    </use>
    </svg>
    </label>
    <label data-tip="folder">
    <input type="radio" name="pick">
    <svg class="ns-icon" aria-hidden="true">
    <use href="#i-folder">
    </use>
    </svg>
    </label>
    <label data-tip="star">
    <input type="radio" name="pick">
    <svg class="ns-icon" aria-hidden="true">
    <use href="#i-star">
    </use>
    </svg>
    </label>
    <label data-tip="map-pin">
    <input type="radio" name="pick">
    <svg class="ns-icon" aria-hidden="true">
    <use href="#i-map-pin">
    </use>
    </svg>
    </label>
    <label data-tip="credit-card">
    <input type="radio" name="pick">
    <svg class="ns-icon" aria-hidden="true">
    <use href="#i-credit-card">
    </use>
    </svg>
    </label>
    <label data-tip="bar-chart">
    <input type="radio" name="pick">
    <svg class="ns-icon" aria-hidden="true">
    <use href="#i-bar-chart">
    </use>
    </svg>
    </label>
    <label data-tip="camera">
    <input type="radio" name="pick">
    <svg class="ns-icon" aria-hidden="true">
    <use href="#i-camera">
    </use>
    </svg>
    </label>
    <label data-tip="lock">
    <input type="radio" name="pick">
    <svg class="ns-icon" aria-hidden="true">
    <use href="#i-lock">
    </use>
    </svg>
    </label>
    <label data-tip="send">
    <input type="radio" name="pick">
    <svg class="ns-icon" aria-hidden="true">
    <use href="#i-send">
    </use>
    </svg>
    </label>
    <label data-tip="heart">
    <input type="radio" name="pick">
    <svg class="ns-icon" aria-hidden="true">
    <use href="#i-heart">
    </use>
    </svg>
    </label>
    </fieldset>
    The whole set

    Hover any of them for its name. They are sized in em, so they follow the text size knob in the bar.

    Source
    <div class="ns-row ns-gap-3 ns-middle">
    <span data-tip="home">
    <svg class="ns-icon ns-lg" aria-hidden="true">
    <use href="#i-home">
    </use>
    </svg>
    </span>
    <span data-tip="user">
    <svg class="ns-icon ns-lg" aria-hidden="true">
    <use href="#i-user">
    </use>
    </svg>
    </span>
    <span data-tip="users">
    <svg class="ns-icon ns-lg" aria-hidden="true">
    <use href="#i-users">
    </use>
    </svg>
    </span>
    <span data-tip="settings">
    <svg class="ns-icon ns-lg" aria-hidden="true">
    <use href="#i-settings">
    </use>
    </svg>
    </span>
    <span data-tip="search">
    <svg class="ns-icon ns-lg" aria-hidden="true">
    <use href="#i-search">
    </use>
    </svg>
    </span>
    <span data-tip="mail">
    <svg class="ns-icon ns-lg" aria-hidden="true">
    <use href="#i-mail">
    </use>
    </svg>
    </span>
    <span data-tip="send">
    <svg class="ns-icon ns-lg" aria-hidden="true">
    <use href="#i-send">
    </use>
    </svg>
    </span>
    <span data-tip="inbox">
    <svg class="ns-icon ns-lg" aria-hidden="true">
    <use href="#i-inbox">
    </use>
    </svg>
    </span>
    <span data-tip="archive">
    <svg class="ns-icon ns-lg" aria-hidden="true">
    <use href="#i-archive">
    </use>
    </svg>
    </span>
    <span data-tip="paperclip">
    <svg class="ns-icon ns-lg" aria-hidden="true">
    <use href="#i-paperclip">
    </use>
    </svg>
    </span>
    <span data-tip="calendar">
    <svg class="ns-icon ns-lg" aria-hidden="true">
    <use href="#i-calendar">
    </use>
    </svg>
    </span>
    <span data-tip="clock">
    <svg class="ns-icon ns-lg" aria-hidden="true">
    <use href="#i-clock">
    </use>
    </svg>
    </span>
    <span data-tip="file-text">
    <svg class="ns-icon ns-lg" aria-hidden="true">
    <use href="#i-file-text">
    </use>
    </svg>
    </span>
    <span data-tip="folder">
    <svg class="ns-icon ns-lg" aria-hidden="true">
    <use href="#i-folder">
    </use>
    </svg>
    </span>
    <span data-tip="bell">
    <svg class="ns-icon ns-lg" aria-hidden="true">
    <use href="#i-bell">
    </use>
    </svg>
    </span>
    <span data-tip="star">
    <svg class="ns-icon ns-lg" aria-hidden="true">
    <use href="#i-star">
    </use>
    </svg>
    </span>
    <span data-tip="heart">
    <svg class="ns-icon ns-lg" aria-hidden="true">
    <use href="#i-heart">
    </use>
    </svg>
    </span>
    <span data-tip="check">
    <svg class="ns-icon ns-lg" aria-hidden="true">
    <use href="#i-check">
    </use>
    </svg>
    </span>
    <span data-tip="x">
    <svg class="ns-icon ns-lg" aria-hidden="true">
    <use href="#i-x">
    </use>
    </svg>
    </span>
    <span data-tip="plus">
    <svg class="ns-icon ns-lg" aria-hidden="true">
    <use href="#i-plus">
    </use>
    </svg>
    </span>
    <span data-tip="minus">
    <svg class="ns-icon ns-lg" aria-hidden="true">
    <use href="#i-minus">
    </use>
    </svg>
    </span>
    <span data-tip="edit">
    <svg class="ns-icon ns-lg" aria-hidden="true">
    <use href="#i-edit">
    </use>
    </svg>
    </span>
    <span data-tip="trash">
    <svg class="ns-icon ns-lg" aria-hidden="true">
    <use href="#i-trash">
    </use>
    </svg>
    </span>
    <span data-tip="save">
    <svg class="ns-icon ns-lg" aria-hidden="true">
    <use href="#i-save">
    </use>
    </svg>
    </span>
    <span data-tip="copy">
    <svg class="ns-icon ns-lg" aria-hidden="true">
    <use href="#i-copy">
    </use>
    </svg>
    </span>
    <span data-tip="download">
    <svg class="ns-icon ns-lg" aria-hidden="true">
    <use href="#i-download">
    </use>
    </svg>
    </span>
    <span data-tip="upload">
    <svg class="ns-icon ns-lg" aria-hidden="true">
    <use href="#i-upload">
    </use>
    </svg>
    </span>
    <span data-tip="lock">
    <svg class="ns-icon ns-lg" aria-hidden="true">
    <use href="#i-lock">
    </use>
    </svg>
    </span>
    <span data-tip="unlock">
    <svg class="ns-icon ns-lg" aria-hidden="true">
    <use href="#i-unlock">
    </use>
    </svg>
    </span>
    <span data-tip="eye">
    <svg class="ns-icon ns-lg" aria-hidden="true">
    <use href="#i-eye">
    </use>
    </svg>
    </span>
    <span data-tip="eye-off">
    <svg class="ns-icon ns-lg" aria-hidden="true">
    <use href="#i-eye-off">
    </use>
    </svg>
    </span>
    <span data-tip="chevron-down">
    <svg class="ns-icon ns-lg" aria-hidden="true">
    <use href="#i-chevron-down">
    </use>
    </svg>
    </span>
    <span data-tip="chevron-up">
    <svg class="ns-icon ns-lg" aria-hidden="true">
    <use href="#i-chevron-up">
    </use>
    </svg>
    </span>
    <span data-tip="chevron-left">
    <svg class="ns-icon ns-lg" aria-hidden="true">
    <use href="#i-chevron-left">
    </use>
    </svg>
    </span>
    <span data-tip="chevron-right">
    <svg class="ns-icon ns-lg" aria-hidden="true">
    <use href="#i-chevron-right">
    </use>
    </svg>
    </span>
    <span data-tip="arrow-left">
    <svg class="ns-icon ns-lg" aria-hidden="true">
    <use href="#i-arrow-left">
    </use>
    </svg>
    </span>
    <span data-tip="arrow-right">
    <svg class="ns-icon ns-lg" aria-hidden="true">
    <use href="#i-arrow-right">
    </use>
    </svg>
    </span>
    <span data-tip="menu">
    <svg class="ns-icon ns-lg" aria-hidden="true">
    <use href="#i-menu">
    </use>
    </svg>
    </span>
    <span data-tip="grid">
    <svg class="ns-icon ns-lg" aria-hidden="true">
    <use href="#i-grid">
    </use>
    </svg>
    </span>
    <span data-tip="list">
    <svg class="ns-icon ns-lg" aria-hidden="true">
    <use href="#i-list">
    </use>
    </svg>
    </span>
    <span data-tip="filter">
    <svg class="ns-icon ns-lg" aria-hidden="true">
    <use href="#i-filter">
    </use>
    </svg>
    </span>
    <span data-tip="more-horizontal">
    <svg class="ns-icon ns-lg" aria-hidden="true">
    <use href="#i-more-horizontal">
    </use>
    </svg>
    </span>
    <span data-tip="refresh">
    <svg class="ns-icon ns-lg" aria-hidden="true">
    <use href="#i-refresh">
    </use>
    </svg>
    </span>
    <span data-tip="map-pin">
    <svg class="ns-icon ns-lg" aria-hidden="true">
    <use href="#i-map-pin">
    </use>
    </svg>
    </span>
    <span data-tip="phone">
    <svg class="ns-icon ns-lg" aria-hidden="true">
    <use href="#i-phone">
    </use>
    </svg>
    </span>
    <span data-tip="message-square">
    <svg class="ns-icon ns-lg" aria-hidden="true">
    <use href="#i-message-square">
    </use>
    </svg>
    </span>
    <span data-tip="camera">
    <svg class="ns-icon ns-lg" aria-hidden="true">
    <use href="#i-camera">
    </use>
    </svg>
    </span>
    <span data-tip="image">
    <svg class="ns-icon ns-lg" aria-hidden="true">
    <use href="#i-image">
    </use>
    </svg>
    </span>
    <span data-tip="link">
    <svg class="ns-icon ns-lg" aria-hidden="true">
    <use href="#i-link">
    </use>
    </svg>
    </span>
    <span data-tip="external-link">
    <svg class="ns-icon ns-lg" aria-hidden="true">
    <use href="#i-external-link">
    </use>
    </svg>
    </span>
    <span data-tip="log-out">
    <svg class="ns-icon ns-lg" aria-hidden="true">
    <use href="#i-log-out">
    </use>
    </svg>
    </span>
    <span data-tip="alert-triangle">
    <svg class="ns-icon ns-lg" aria-hidden="true">
    <use href="#i-alert-triangle">
    </use>
    </svg>
    </span>
    <span data-tip="info">
    <svg class="ns-icon ns-lg" aria-hidden="true">
    <use href="#i-info">
    </use>
    </svg>
    </span>
    <span data-tip="credit-card">
    <svg class="ns-icon ns-lg" aria-hidden="true">
    <use href="#i-credit-card">
    </use>
    </svg>
    </span>
    <span data-tip="shopping-cart">
    <svg class="ns-icon ns-lg" aria-hidden="true">
    <use href="#i-shopping-cart">
    </use>
    </svg>
    </span>
    <span data-tip="bar-chart">
    <svg class="ns-icon ns-lg" aria-hidden="true">
    <use href="#i-bar-chart">
    </use>
    </svg>
    </span>
    <span data-tip="trending-up">
    <svg class="ns-icon ns-lg" aria-hidden="true">
    <use href="#i-trending-up">
    </use>
    </svg>
    </span>
    <span data-tip="sun">
    <svg class="ns-icon ns-lg" aria-hidden="true">
    <use href="#i-sun">
    </use>
    </svg>
    </span>
    <span data-tip="moon">
    <svg class="ns-icon ns-lg" aria-hidden="true">
    <use href="#i-moon">
    </use>
    </svg>
    </span>
    </div>

    Slides

    A carousel with no script at all: scroll-snap for the movement and the browser's own ::scroll-marker for the dots. Where a browser lacks the pseudo-element the fallback is not broken, it is a horizontally scrolling strip — which is a usable carousel.

    One at a time

    Drag it, or use the dots underneath. Keyboard scrolling works because it is a scroll container and nothing else.

    Bring your own database

    A customer points Enclave at their own Turso instance. We never hold their data.

    Bring your own model

    One key, kept encrypted, used for that organization and no other.

    Bring your own bucket

    Files land in the customer's S3, under the customer's retention rules.

    Source
    <div class="ns-slides">
    <article class="ns-card ns-primary">
    <h3>Bring your own database</h3>
    <p class="ns-muted">A customer points Enclave at their own Turso instance. We never hold their data.</p>
    </article>
    <article class="ns-card ns-cta">
    <h3>Bring your own model</h3>
    <p class="ns-muted">One key, kept encrypted, used for that organization and no other.</p>
    </article>
    <article class="ns-card ns-success">
    <h3>Bring your own bucket</h3>
    <p class="ns-muted">Files land in the customer's S3, under the customer's retention rules.</p>
    </article>
    </div>

    App blocks

    Four shapes with rules of their own, each scoped with @scope so they cannot leak. A feature never writes CSS; these four exist because a kanban column, a chat thread, a desktop and a month are shapes no element carries.

    Kanban

    Moving a card is a select inside its form, never a drag — a drag needs a script, and a select works on a phone and with a keyboard.

    • To do

    • Order the tiles

      Due Friday

    • Call the electrician

      Dana

    • Doing

    • Strip the old decking

      Marco

    • Done

    • Measure the terrace

      Done 2 Oct

    Source
    <section class="ns-board">
    <ul>
    <li>
    <h3>To do</h3>
    </li>
    <li>
    <article>
    <strong>Order the tiles</strong>
    <p class="ns-small ns-muted">Due Friday</p>
    </article>
    </li>
    <li>
    <article>
    <strong>Call the electrician</strong>
    <p class="ns-small ns-muted">Dana</p>
    </article>
    </li>
    </ul>
    <ul>
    <li>
    <h3>Doing</h3>
    </li>
    <li>
    <article>
    <strong>Strip the old decking</strong>
    <p class="ns-small ns-muted">Marco</p>
    </article>
    </li>
    </ul>
    <ul>
    <li>
    <h3>Done</h3>
    </li>
    <li>
    <article>
    <strong>Measure the terrace</strong>
    <p class="ns-small ns-muted">Done 2 Oct</p>
    </article>
    </li>
    </ul>
    </section>
    Chat thread

    Realtime is a meta refresh on the open conversation. The thread itself does not need to know that.

    1. Dana

      Are the allergen sheets printed for tonight?

    2. You

      Printed and on the pass.

    3. Marco

      Thanks — I updated the sheet for the new supplier.

    Source
    <ol class="ns-thread">
    <li>
    <article>
    <header>
    <strong>Dana</strong>
    <time datetime="2026-10-12T09:02">09:02</time>
    </header>
    <p>Are the allergen sheets printed for tonight?</p>
    </article>
    </li>
    <li data-mine>
    <article>
    <header>
    <strong>You</strong>
    <time datetime="2026-10-12T09:04">09:04</time>
    </header>
    <p>Printed and on the pass.</p>
    </article>
    </li>
    <li>
    <article>
    <header>
    <strong>Marco</strong>
    <time datetime="2026-10-12T09:06">09:06</time>
    </header>
    <p>Thanks &#8212; I updated the sheet for the new supplier.</p>
    </article>
    </li>
    </ol>
    Desktop

    App icons that are links, each with its count as a data element.

    Source
    <nav class="ns-desktop" aria-label="Apps">
    <a href="#apps">
    <svg viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.5">
    <rect x="3" y="3" width="18" height="18" rx="3"/>
    </svg>
    <span>Feed</span>
    <data value="3">3</data>
    </a>
    <a href="#apps">
    <svg viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.5">
    <rect x="3" y="3" width="18" height="18" rx="3"/>
    </svg>
    <span>Documents</span>
    </a>
    <a href="#apps">
    <svg viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.5">
    <rect x="3" y="3" width="18" height="18" rx="3"/>
    </svg>
    <span>Calendar</span>
    </a>
    <a href="#apps">
    <svg viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.5">
    <rect x="3" y="3" width="18" height="18" rx="3"/>
    </svg>
    <span>Projects</span>
    <data value="7">7</data>
    </a>
    <a href="#apps">
    <svg viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.5">
    <rect x="3" y="3" width="18" height="18" rx="3"/>
    </svg>
    <span>Money</span>
    </a>
    <a href="#apps">
    <svg viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.5">
    <rect x="3" y="3" width="18" height="18" rx="3"/>
    </svg>
    <span>Chat</span>
    </a>
    <a href="#apps">
    <svg viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.5">
    <rect x="3" y="3" width="18" height="18" rx="3"/>
    </svg>
    <span>Files</span>
    </a>
    <a href="#apps">
    <svg viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.5">
    <rect x="3" y="3" width="18" height="18" rx="3"/>
    </svg>
    <span>System Settings</span>
    </a>
    </nav>
    Calendar

    A month IS tabular — days across, weeks down — so it is a table, and the three views are the same block with a different shape of data in it. The toggle is three radios read by :has(); no script decides what you are looking at. Narrow the window and the month becomes a day list, because a seven-column grid on a phone is unreadable.

    Source
    <div class="ns-stack">
    <div class="ns-row ns-between ns-middle">
    <strong>October 2026</strong>
    <fieldset class="ns-segmented ns-sm">
    <legend class="ns-sr">View</legend>
    <label>
    <input type="radio" name="calview" id="cal-month" checked>Month</label>
    <label>
    <input type="radio" name="calview" id="cal-week">Week</label>
    <label>
    <input type="radio" name="calview" id="cal-day">Day</label>
    </fieldset>
    </div>
    <table class="ns-calendar" data-view="month">
    <caption class="ns-sr">October 2026, by month</caption>
    <thead>
    <tr>
    <th>Mon</th>
    <th>Tue</th>
    <th>Wed</th>
    <th>Thu</th>
    <th>Fri</th>
    <th>Sat</th>
    <th>Sun</th>
    </tr>
    </thead>
    <tbody>
    <tr>
    <td data-outside>
    </td>
    <td data-outside>
    </td>
    <td data-outside>
    </td>
    <td>
    <a href="#apps-a-month">1</a>
    <ul>
    <li class="ns-primary">
    <a href="#apps-a-month">Standup</a>
    </li>
    </ul>
    </td>
    <td>
    <a href="#apps-a-month">2</a>
    <ul>
    <li class="ns-cta">
    <a href="#apps-a-month">Invoice run</a>
    </li>
    </ul>
    </td>
    <td>
    <a href="#apps-a-month">3</a>
    </td>
    <td>
    <a href="#apps-a-month">4</a>
    </td>
    </tr>
    <tr>
    <td>
    <a href="#apps-a-month">5</a>
    </td>
    <td>
    <a href="#apps-a-month">6</a>
    <ul>
    <li class="ns-primary">
    <a href="#apps-a-month">Design review</a>
    </li>
    <li class="ns-secondary">
    <a href="#apps-a-month">1:1 with Bo</a>
    </li>
    </ul>
    </td>
    <td>
    <a href="#apps-a-month">7</a>
    </td>
    <td>
    <a href="#apps-a-month">8</a>
    <ul>
    <li class="ns-success">
    <a href="#apps-a-month">Ship 0.1.0</a>
    </li>
    </ul>
    </td>
    <td>
    <a href="#apps-a-month">9</a>
    </td>
    <td>
    <a href="#apps-a-month">10</a>
    </td>
    <td>
    <a href="#apps-a-month">11</a>
    </td>
    </tr>
    <tr>
    <td>
    <a href="#apps-a-month">12</a>
    <ul>
    <li class="ns-primary">
    <a href="#apps-a-month">Standup</a>
    </li>
    <li class="ns-warn">
    <a href="#apps-a-month">Board prep</a>
    </li>
    </ul>
    </td>
    <td>
    <a href="#apps-a-month">13</a>
    </td>
    <td data-today>
    <a href="#apps-a-month">14</a>
    <ul>
    <li class="ns-cta">
    <a href="#apps-a-month">Client call</a>
    </li>
    <li class="ns-primary">
    <a href="#apps-a-month">Retro</a>
    </li>
    <li class="ns-success">
    <a href="#apps-a-month">Payroll</a>
    </li>
    </ul>
    </td>
    <td>
    <a href="#apps-a-month">15</a>
    <ul>
    <li class="ns-primary">
    <a href="#apps-a-month">Onboarding</a>
    </li>
    </ul>
    </td>
    <td>
    <a href="#apps-a-month">16</a>
    </td>
    <td>
    <a href="#apps-a-month">17</a>
    </td>
    <td>
    <a href="#apps-a-month">18</a>
    </td>
    </tr>
    <tr>
    <td>
    <a href="#apps-a-month">19</a>
    <ul>
    <li class="ns-primary">
    <a href="#apps-a-month">Standup</a>
    </li>
    </ul>
    </td>
    <td>
    <a href="#apps-a-month">20</a>
    </td>
    <td>
    <a href="#apps-a-month">21</a>
    <ul>
    <li class="ns-danger">
    <a href="#apps-a-month">Security review</a>
    </li>
    </ul>
    </td>
    <td>
    <a href="#apps-a-month">22</a>
    </td>
    <td>
    <a href="#apps-a-month">23</a>
    <ul>
    <li class="ns-cta">
    <a href="#apps-a-month">Quarter close</a>
    </li>
    </ul>
    </td>
    <td>
    <a href="#apps-a-month">24</a>
    </td>
    <td>
    <a href="#apps-a-month">25</a>
    </td>
    </tr>
    <tr>
    <td>
    <a href="#apps-a-month">26</a>
    </td>
    <td>
    <a href="#apps-a-month">27</a>
    </td>
    <td>
    <a href="#apps-a-month">28</a>
    <ul>
    <li class="ns-primary">
    <a href="#apps-a-month">Standup</a>
    </li>
    </ul>
    </td>
    <td>
    <a href="#apps-a-month">29</a>
    </td>
    <td>
    <a href="#apps-a-month">30</a>
    <ul>
    <li class="ns-success">
    <a href="#apps-a-month">All hands</a>
    </li>
    </ul>
    </td>
    <td>
    <a href="#apps-a-month">31</a>
    </td>
    <td data-outside>
    </td>
    </tr>
    </tbody>
    </table>
    <table class="ns-calendar" data-view="week">
    <caption class="ns-sr">Week of 12 October 2026</caption>
    <thead>
    <tr>
    <th>Mon 12</th>
    <th>Tue 13</th>
    <th>Wed 14</th>
    <th>Thu 15</th>
    <th>Fri 16</th>
    <th>Sat 17</th>
    <th>Sun 18</th>
    </tr>
    </thead>
    <tbody>
    <tr>
    <td>
    <a href="#apps-a-month">12</a>
    <ul>
    <li class="ns-primary">
    <a href="#apps-a-month">09:00 Standup</a>
    </li>
    <li class="ns-warn">
    <a href="#apps-a-month">14:00 Board prep</a>
    </li>
    </ul>
    </td>
    <td>
    <a href="#apps-a-month">13</a>
    <ul>
    <li class="ns-secondary">
    <a href="#apps-a-month">11:00 Interview</a>
    </li>
    </ul>
    </td>
    <td data-today>
    <a href="#apps-a-month">14</a>
    <ul>
    <li class="ns-primary">
    <a href="#apps-a-month">09:00 Standup</a>
    </li>
    <li class="ns-cta">
    <a href="#apps-a-month">10:30 Client call</a>
    </li>
    <li class="ns-primary">
    <a href="#apps-a-month">15:00 Retro</a>
    </li>
    <li class="ns-success">
    <a href="#apps-a-month">16:30 Payroll</a>
    </li>
    </ul>
    </td>
    <td>
    <a href="#apps-a-month">15</a>
    <ul>
    <li class="ns-primary">
    <a href="#apps-a-month">09:00 Standup</a>
    </li>
    <li class="ns-primary">
    <a href="#apps-a-month">13:00 Onboarding</a>
    </li>
    </ul>
    </td>
    <td>
    <a href="#apps-a-month">16</a>
    <ul>
    <li class="ns-primary">
    <a href="#apps-a-month">09:00 Standup</a>
    </li>
    <li class="ns-success">
    <a href="#apps-a-month">17:00 Ship it</a>
    </li>
    </ul>
    </td>
    <td>
    <a href="#apps-a-month">17</a>
    <ul>
    </ul>
    </td>
    <td>
    <a href="#apps-a-month">18</a>
    <ul>
    </ul>
    </td>
    </tr>
    </tbody>
    </table>
    <table class="ns-calendar" data-view="day">
    <caption class="ns-sr">Wednesday 14 October 2026</caption>
    <tbody>
    <tr>
    <td>
    <a href="#apps-a-month">08:00</a>
    </td>
    </tr>
    <tr>
    <td>
    <a href="#apps-a-month">09:00</a>
    <ul>
    <li class="ns-primary">
    <a href="#apps-a-month">Standup</a>
    </li>
    </ul>
    </td>
    </tr>
    <tr>
    <td>
    <a href="#apps-a-month">10:00</a>
    </td>
    </tr>
    <tr>
    <td>
    <a href="#apps-a-month">10:30</a>
    <ul>
    <li class="ns-cta">
    <a href="#apps-a-month">Client call — Ingrid Meyer</a>
    </li>
    </ul>
    </td>
    </tr>
    <tr>
    <td>
    <a href="#apps-a-month">12:00</a>
    </td>
    </tr>
    <tr>
    <td>
    <a href="#apps-a-month">13:00</a>
    </td>
    </tr>
    <tr>
    <td>
    <a href="#apps-a-month">15:00</a>
    <ul>
    <li class="ns-primary">
    <a href="#apps-a-month">Retro</a>
    </li>
    </ul>
    </td>
    </tr>
    <tr>
    <td>
    <a href="#apps-a-month">16:30</a>
    <ul>
    <li class="ns-success">
    <a href="#apps-a-month">Payroll</a>
    </li>
    </ul>
    </td>
    </tr>
    <tr>
    <td>
    <a href="#apps-a-month">17:00</a>
    </td>
    </tr>
    <tr>
    <td>
    <a href="#apps-a-month">18:00</a>
    </td>
    </tr>
    </tbody>
    </table>
    </div>

    Whole screens

    Three screens assembled only from what is above, to check the thing a gallery of parts cannot: whether the system composes. Change the theme or the corner radius in the bar and watch all three move together — none of them carries a colour or a shape of its own.

    A mail client

    A list and a message, from the grid, the table, the avatar and the badge. Nothing here is a mail component.

    Invoice 2026-04 is readyIngrid Meyer · 09:12

    April is closed. The invoice covers the two extra seats you added on the 14th, prorated, and the storage overage is on a separate line this time so it is easier to argue with.

    invoice-2026-04.pdf62 KB

    Source
    <div class="ns-grid ns-gap-0" style="border:var(--border) solid var(--outline);border-radius:var(--radius);overflow:hidden">
    <div class="ns-span-4 ns-stack ns-gap-0" style="border-inline-end:var(--border) solid var(--outline)">
    <div class="ns-row ns-between ns-middle ns-pad-3" style="border-block-end:var(--border) solid var(--outline)">
    <strong class="ns-small">Inbox</strong>
    <span class="ns-badge ns-primary">3</span>
    </div>
    <a class="ns-row ns-gap-2 ns-pad-3" href="#mockups-a-mail-client" aria-current="page" style="border-block-end:var(--border) solid var(--outline);background:var(--highlight);text-decoration:none">
    <span class="ns-avatar ns-sm">IM</span>
    <span class="ns-stack ns-gap-1">
    <strong class="ns-small">Ingrid Meyer</strong>
    <span class="ns-small ns-muted ns-truncate">Invoice 2026-04 is ready</span>
    </span>
    </a>
    <a class="ns-row ns-gap-2 ns-pad-3" href="#mockups-a-mail-client" style="border-block-end:var(--border) solid var(--outline);text-decoration:none">
    <span class="ns-avatar ns-sm">CW</span>
    <span class="ns-stack ns-gap-1">
    <strong class="ns-small">Chen Wei</strong>
    <span class="ns-small ns-muted ns-truncate">Re: onboarding checklist</span>
    </span>
    </a>
    <a class="ns-row ns-gap-2 ns-pad-3" href="#mockups-a-mail-client" style="text-decoration:none">
    <span class="ns-avatar ns-sm">DH</span>
    <span class="ns-stack ns-gap-1">
    <strong class="ns-small">Dalia Haddad</strong>
    <span class="ns-small ns-muted ns-truncate">Q3 numbers, first pass</span>
    </span>
    </a>
    </div>
    <div class="ns-span-8 ns-stack ns-pad-4">
    <div class="ns-row ns-between ns-middle">
    <div class="ns-stack ns-gap-1">
    <strong>Invoice 2026-04 is ready</strong>
    <span class="ns-small ns-muted">Ingrid Meyer · 09:12</span>
    </div>
    <div class="ns-row ns-gap-1">
    <button class="ns-ghost ns-square ns-sm" data-tip="Archive">
    <svg class="ns-icon" aria-hidden="true">
    <use href="#i-archive">
    </use>
    </svg>
    <span class="ns-sr">Archive</span>
    </button>
    <button class="ns-ghost ns-square ns-sm" data-tip="Delete">
    <svg class="ns-icon" aria-hidden="true">
    <use href="#i-trash">
    </use>
    </svg>
    <span class="ns-sr">Delete</span>
    </button>
    <button class="ns-ghost ns-square ns-sm" popovertarget="m-mail" data-tip="More">
    <svg class="ns-icon" aria-hidden="true">
    <use href="#i-more-horizontal">
    </use>
    </svg>
    <span class="ns-sr">More</span>
    </button>
    <ul popover id="m-mail" class="ns-menu">
    <li>
    <a href="#mockups-a-mail-client">Mark unread</a>
    </li>
    <li>
    <a href="#mockups-a-mail-client">Move to…</a>
    </li>
    <li>
    <a href="#mockups-a-mail-client">Print</a>
    </li>
    </ul>
    </div>
    </div>
    <p>April is closed. The invoice covers the two extra seats you added on the 14th, prorated, and the storage overage is on a separate line this time so it is easier to argue with.</p>
    <p class="ns-row ns-gap-2 ns-middle">
    <span class="ns-badge">
    <svg class="ns-icon ns-sm" aria-hidden="true">
    <use href="#i-paperclip">
    </use>
    </svg>invoice-2026-04.pdf</span>
    <span class="ns-badge">62 KB</span>
    </p>
    <div class="ns-row ns-gap-2">
    <button class="ns-primary">
    <svg class="ns-icon" aria-hidden="true">
    <use href="#i-send">
    </use>
    </svg>Reply</button>
    <button>Forward</button>
    </div>
    </div>
    </div>
    A calculator

    Only the grid, the tones and the button. The display is an <output>, which is what it is. It is 700px wide on purpose: below 640px of container every column spans twelve, which is the grid's rule and not a bug — a calculator on a phone is four ns-rows, not this.

    1 248.75
    Source
    <div class="ns-card ns-stack" style="max-inline-size:700px">
    <output class="ns-mono ns-right" style="font-size:2rem;padding:var(--s-3);background:var(--highlight);border-radius:var(--radius)">1 248.75</output>
    <div class="ns-grid ns-gap-2">
    <div class="ns-span-3">
    <button class="ns-muted ns-full">C</button>
    </div>
    <div class="ns-span-3">
    <button class="ns-muted ns-full">±</button>
    </div>
    <div class="ns-span-3">
    <button class="ns-muted ns-full">%</button>
    </div>
    <div class="ns-span-3">
    <button class="ns-cta ns-full">÷</button>
    </div>
    <div class="ns-span-3">
    <button class="ns-full">7</button>
    </div>
    <div class="ns-span-3">
    <button class="ns-full">8</button>
    </div>
    <div class="ns-span-3">
    <button class="ns-full">9</button>
    </div>
    <div class="ns-span-3">
    <button class="ns-cta ns-full">×</button>
    </div>
    <div class="ns-span-3">
    <button class="ns-full">4</button>
    </div>
    <div class="ns-span-3">
    <button class="ns-full">5</button>
    </div>
    <div class="ns-span-3">
    <button class="ns-full">6</button>
    </div>
    <div class="ns-span-3">
    <button class="ns-cta ns-full">−</button>
    </div>
    <div class="ns-span-3">
    <button class="ns-full">1</button>
    </div>
    <div class="ns-span-3">
    <button class="ns-full">2</button>
    </div>
    <div class="ns-span-3">
    <button class="ns-full">3</button>
    </div>
    <div class="ns-span-3">
    <button class="ns-cta ns-full">+</button>
    </div>
    <div class="ns-span-6">
    <button class="ns-full">0</button>
    </div>
    <div class="ns-span-3">
    <button class="ns-full">,</button>
    </div>
    <div class="ns-span-3">
    <button class="ns-primary ns-full">=</button>
    </div>
    </div>
    </div>
    An admin panel

    Cards, a chart, a sticky table and a menu. This is the shape most screens in the product will take.

    This month

    Members248+12 this month
    Revenue€ 9 412+4.1%
    Storage81%
    Signups per week+38%
    MemberRoleSeenActions
    AOAda OkonjoOwner2 minutes ago
    BLBo LindqvistMemberYesterday
    CWChen WeiMemberLast week
    DHDalia HaddadClientNever
    Source
    <div class="ns-stack">
    <div class="ns-row ns-between ns-middle">
    <h3 class="ns-m-0">This month</h3>
    <div class="ns-row ns-gap-2">
    <input type="search" placeholder="Filter" aria-label="Filter">
    <button class="ns-primary">
    <svg class="ns-icon" aria-hidden="true">
    <use href="#i-plus">
    </use>
    </svg>Invite</button>
    </div>
    </div>
    <div class="ns-grid ns-gap-3">
    <div class="ns-span-4">
    <div class="ns-card ns-stack ns-gap-1">
    <span class="ns-small ns-muted">Members</span>
    <strong style="font-size:1.6rem">248</strong>
    <span class="ns-small ns-success">
    <svg class="ns-icon ns-sm" aria-hidden="true">
    <use href="#i-trending-up">
    </use>
    </svg>+12 this month</span>
    </div>
    </div>
    <div class="ns-span-4">
    <div class="ns-card ns-stack ns-gap-1">
    <span class="ns-small ns-muted">Revenue</span>
    <strong style="font-size:1.6rem">€ 9 412</strong>
    <span class="ns-small ns-success">
    <svg class="ns-icon ns-sm" aria-hidden="true">
    <use href="#i-trending-up">
    </use>
    </svg>+4.1%</span>
    </div>
    </div>
    <div class="ns-span-4">
    <div class="ns-card ns-stack ns-gap-1">
    <span class="ns-small ns-muted">Storage</span>
    <strong style="font-size:1.6rem">81%</strong>
    <div class="ns-warn">
    <progress value="0.81" aria-label="Storage used">
    </progress>
    </div>
    </div>
    </div>
    </div>
    <div class="ns-card ns-stack">
    <div class="ns-row ns-between ns-middle">
    <strong class="ns-small">Signups per week</strong>
    <span class="ns-badge ns-success">+38%</span>
    </div>
    <div class="ns-chart ns-primary" data-chart="bar">
    <div style="--v:.28" data-label="W1" data-value="18">
    </div>
    <div style="--v:.36" data-label="W2" data-value="23">
    </div>
    <div style="--v:.52" data-label="W3" data-value="34">
    </div>
    <div style="--v:.47" data-label="W4" data-value="31">
    </div>
    <div style="--v:.71" data-label="W5" data-value="46">
    </div>
    <div style="--v:.86" data-label="W6" data-value="55">
    </div>
    <div style="--v:1" data-label="W7" data-value="64">
    </div>
    </div>
    </div>
    <table>
    <thead class="ns-sticky">
    <tr>
    <th>Member</th>
    <th>Role</th>
    <th>Seen</th>
    <th class="ns-right">Actions</th>
    </tr>
    </thead>
    <tbody>
    <tr>
    <td class="ns-row ns-gap-2 ns-middle">
    <span class="ns-avatar ns-sm">AO</span>Ada Okonjo</td>
    <td>
    <span class="ns-badge ns-primary">Owner</span>
    </td>
    <td class="ns-muted ns-small">2 minutes ago</td>
    <td class="ns-right">
    <button class="ns-ghost ns-square ns-sm" data-tip="Edit">
    <svg class="ns-icon" aria-hidden="true">
    <use href="#i-edit">
    </use>
    </svg>
    <span class="ns-sr">Edit Ada Okonjo</span>
    </button>
    </td>
    </tr>
    <tr>
    <td class="ns-row ns-gap-2 ns-middle">
    <span class="ns-avatar ns-sm">BL</span>Bo Lindqvist</td>
    <td>
    <span class="ns-badge">Member</span>
    </td>
    <td class="ns-muted ns-small">Yesterday</td>
    <td class="ns-right">
    <button class="ns-ghost ns-square ns-sm" data-tip="Edit">
    <svg class="ns-icon" aria-hidden="true">
    <use href="#i-edit">
    </use>
    </svg>
    <span class="ns-sr">Edit Bo Lindqvist</span>
    </button>
    </td>
    </tr>
    <tr>
    <td class="ns-row ns-gap-2 ns-middle">
    <span class="ns-avatar ns-sm">CW</span>Chen Wei</td>
    <td>
    <span class="ns-badge">Member</span>
    </td>
    <td class="ns-muted ns-small">Last week</td>
    <td class="ns-right">
    <button class="ns-ghost ns-square ns-sm" data-tip="Edit">
    <svg class="ns-icon" aria-hidden="true">
    <use href="#i-edit">
    </use>
    </svg>
    <span class="ns-sr">Edit Chen Wei</span>
    </button>
    </td>
    </tr>
    <tr>
    <td class="ns-row ns-gap-2 ns-middle">
    <span class="ns-avatar ns-sm">DH</span>Dalia Haddad</td>
    <td>
    <span class="ns-badge ns-muted">Client</span>
    </td>
    <td class="ns-muted ns-small">Never</td>
    <td class="ns-right">
    <button class="ns-ghost ns-square ns-sm" data-tip="Edit">
    <svg class="ns-icon" aria-hidden="true">
    <use href="#i-edit">
    </use>
    </svg>
    <span class="ns-sr">Edit Dalia Haddad</span>
    </button>
    </td>
    </tr>
    </tbody>
    </table>
    </div>