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.
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.
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.
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>
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.
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.
<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>
CalloutsSource
<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>
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.
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.
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.
Breadcrumbs are an ordered list, tabs are links with role=tab because each tab is a page, and the current item says so with aria-current rather than a class.
The page you are reading has one, and that is exactly why this example was missing for so long: a catalogue that USES a component is not a catalogue that documents it. Three cells, 3/6/3, no wrapper div.
What the bar carries: the avatar IS the button, and everything a person reaches for about themselves is behind it — including the theme, because that is the one preference people change on a whim. The theme here is a form that SAVES, not a live preview: it follows the person to their next device. Signing out is a form too, because a GET that ends a session can be triggered by an image tag on any page in the world.
A sidebar STAYS. The page scrolls and the navigation does not, because a menu that scrolls away is a menu you have to scroll back for — and when it is taller than the window it scrolls by itself, so its last entries are reachable exactly when there are enough of them to need it. It stops below the bar rather than under it.
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.
What a 403 or 404 renders. Both codes say the same sentence, because the difference between them is exactly what per-object hiding conceals.
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.
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.
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"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>
<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"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>
<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.
Search
type="search" gets the clear button and the right keyboard on a phone for free. The suggestions are a datalist, so they work with the field typed into rather than instead of it.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.