<button>Default</button><button class="accent">Accent</button><button class="soft">Soft</button><button class="lg">Large</button>
<button class="lg accent">Large accent</button>
<button class="lg soft">Large soft</button><button class="icon" aria-label="Add">
<svg width="20" height="20" viewBox="0 0 24 24"
fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" aria-hidden="true">
<path d="M12 5v14M5 12h14"/>
</svg>
</button>
<button class="icon accent" aria-label="Add">…</button>
<button class="icon soft" aria-label="Add">…</button><button disabled>Disabled</button>
<button class="accent" disabled>Disabled</button>
<button class="soft" disabled>Disabled</button>
<button aria-disabled="true">Aria-disabled</button><a class="button" href="#">Link button</a>
<a class="button accent" href="#">Link accent</a>
<a class="button soft" href="#">Link soft</a><div class="buttons">
<button>One</button>
<button class="accent">Two</button>
<button class="soft">Three</button>
</div><span class="badge">Badge</span>
<span class="badge accent">Accent</span>
<span class="badge success">Success</span>
<span class="badge warning">Warning</span>
<span class="badge danger">Danger</span>
<span class="badge info">Info</span><!-- Sizes: default (inline) / .normal / .lg -->
<span class="badge sm">Inline</span>
<span class="badge">Inline</span>
<span class="badge lg">Normal</span>
<span class="badge xl">Large</span><!-- Count badges: default / .normal / .lg -->
<span class="badge count sm">1</span>
<span class="badge count">2</span>
<span class="badge count lg">3</span>
<span class="badge count xl">3</span>
<!-- Inside a badge -->
<span class="badge">Inbox <span class="badge count">3</span></span>
<span class="badge normal">Inbox <span class="badge count normal">12</span></span><!-- Corner count over a button (parent needs position: relative) -->
<button style="position: relative">
Inbox <span class="badge count corner">4</span>
</button><label for="name">Name</label>
<input id="name" type="text" placeholder="Ada Lovelace"><input type="email" placeholder="you@example.com">
<input type="password" value="hunter2">
<input type="search" placeholder="Search…">
<input type="text" value="Read only" readonly>
<input type="text" value="Disabled" disabled><select>
<option>Design</option>
<option>Engineering</option>
<option>Product</option>
</select><textarea rows="4" placeholder="Say something…"></textarea><label class="center-items" style="gap:.5em;">
<input type="checkbox" checked> Subscribe
</label><label class="center-items" style="gap:.5em;">
<input type="radio" name="plan" checked> Free
</label><form>
<fieldset class="mb-1">
<label for="email">Email</label>
<input id="email" type="email">
</fieldset>
<fieldset class="mb-1">
<label for="msg">Message</label>
<textarea id="msg" rows="3"></textarea>
</fieldset>
<div class="buttons">
<button class="accent" type="submit">Send</button>
<button class="soft" type="reset">Reset</button>
</div>
</form><label class="switch">
<input type="checkbox">
<span class="track"></span>
Dark mode
</label>
<label class="switch">
<input type="checkbox" checked>
<span class="track"></span>
Enabled
</label>
<label class="switch">
<input type="checkbox" disabled>
<span class="track"></span>
Disabled
</label><!-- Switch without label text (uses aria-label) -->
<label class="switch" aria-label="Notifications">
<input type="checkbox">
<span class="track"></span>
</label><!-- Default (inline, slightly larger than text) -->
<label class="avatar">JD</label>
<label class="avatar primary">AB</label>
<label class="avatar accent">CD</label>
<!-- Normal (button-sized) -->
<label class="avatar normal">EF</label>
<!-- Large -->
<label class="avatar lg">GH</label><label class="avatar lg">
<img src="me.jpg" alt="">
</label><div class="alert info" role="status">Info message.</div>
<div class="alert success" role="status">Saved.</div>
<div class="alert warning" role="status">Careful.</div>
<div class="alert danger" role="alert">Something broke.</div>
<div class="alert accent" role="status">Accent.</div><!-- Block (full width) -->
<div class="alert warning block" role="status">
Full-width warning.
</div><!-- Sticky (pins to top of scroll container) -->
<div class="alert info sticky block">
Sticky to the top of the scrollable area.
</div>Scroll me…
<!-- Fixed positions -->
<div class="alert fixed top-left">top-left</div>
<div class="alert fixed top-center">top-center</div>
<div class="alert fixed top-right">top-right</div>
<div class="alert fixed bottom-left">bottom-left</div>
<div class="alert fixed bottom-center">bottom-center</div>
<div class="alert fixed bottom-right">bottom-right</div>
<div class="alert fixed left-center">left-center</div>
<div class="alert fixed right-center">right-center</div>.fixed .top-left etc.
<!-- Toast: fixed + timed (add data-timer + JS removes; CSS animates in) -->
<div class="alert success fixed bottom-right timed"
data-timer="3000" role="status">
Saved.
</div>.alert.fixed.bottom-right.timed for toasts. Add data-timer="ms" and toggle .leaving in JS to fade out.
<details class="disclosure">
<summary>Section title</summary>
<p>Hidden content shown when open.</p>
</details>
<details class="disclosure" open>
<summary>Already open</summary>
<p>This one starts open.</p>
</details>This section is hidden until you expand it.
Wrap a heading in <summary> and put content after it.
This one starts open by default.
<dialog id="demo-dialog">
<h3>Dialog title</h3>
<p>Dialog body text.</p>
<div class="buttons">
<button class="accent" onclick="this.closest('dialog').close()">Close</button>
</div>
</dialog>
<button onclick="document.getElementById('demo-dialog').showModal()">Open dialog</button><section class="panels">
<nav>Navigation</nav>
<main>Main content</main>
<aside>Aside</aside>
</section><!-- Two columns: no aside -->
<section class="panels">
<nav>Navigation</nav>
<main>Main content</main>
</section><!-- Two columns: no nav -->
<section class="panels">
<main>Main content</main>
<aside>Aside</aside>
</section><!-- Single column (no nav, no aside) -->
<section class="panels">
<main>Main content</main>
</section><h1>Heading 1</h1>
<h2>Heading 2</h2>
<h3>Heading 3</h3>
<h4>Heading 4</h4>
<h5>Heading 5</h5>
<h6>Heading 6</h6><p>
A paragraph with an <a href="#">inline link</a>
and some <code>inline code</code>.
</p>
<p class="text-muted">Muted paragraph.</p>A paragraph with an inline link and some inline code.
Muted paragraph.
<ul>
<li>Item one</li>
<li>Item two</li>
<li>Item three</li>
</ul><pre><code>:root {
--padding: 30px;
--radius: 30px;
}</code></pre>:root {
--padding: 30px;
--radius: 30px;
}
<hr>Above the rule.
Below the rule.
<table>
<thead>
<tr><th>Token</th><th>Value</th></tr>
</thead>
<tbody>
<tr><td>--padding</td><td>30px</td></tr>
<tr><td>--radius</td><td>30px</td></tr>
</tbody>
</table>| Token | Value |
|---|---|
--padding | 30px |
--radius | 30px |
--toucharea | 44px |
<div class="bg-odd">…</div>
<div class="bg-alt">…</div>
<div class="no-bg">…</div><p class="mb-1">One padding of space below.</p>
<p class="mb-2">Two paddings of space below.</p>One padding of space below.
Two paddings of space below.
<p class="sr-only">Only screen readers hear this.</p>Only screen readers hear this.
(hidden visually — inspect the DOM)<section>
<h2>Section title</h2>
<p>Lead paragraph.</p>
<div class="buttons">…</div>
</section>Lead paragraph.
<html data-theme="dark">
<!-- or remove the attribute for light --><!-- Theme toggle using the switch component -->
<label class="switch">
<input type="checkbox" id="theme-switch"
onchange="document.documentElement.setAttribute('data-theme', this.checked ? 'dark' : 'light')">
<span class="track"></span>
Dark mode
</label>
The sheet is organised into cascade layers, declared in this order:
reset, tokens, base,
components, utilities.
!important.
The whole theme system is driven by color-scheme. Because every
colour token uses light-dark(), flipping color-scheme
is enough — no token needs to be redefined.
The toggle button sets data-theme on <html>.
The choice is remembered in localStorage, and a tiny script in
<head> applies it before the first paint to avoid a flash.
All margins, paddings and gaps come from a single scale. Change a value in the tokens layer and every element that uses it updates at once — buttons, sections, body padding, utilities.
--page-padding — outer body padding (0 by default)--section-padding — section padding; the token itself changes at 900px, so sections follow automatically--button-padding-block / --button-padding-inline — used by the base button; the .lg variant only redefines these two tokens--gap — default flex/grid gapFont sizes and line heights are tokens too, so the whole hierarchy can be retuned from one place.
Line heights: --line-height-body (2),
--line-height-heading (1.5),
--line-height-section-heading (1.2),
--line-height-section-body (1.55),
--line-height-button (1).
Colours use oklch() and light-dark(), so light
and dark mode come from color-scheme without extra rules.
color-mix() derives hover states from the base colour.
--color-primary, --color-primary-hover--color-accent, --color-accent-text, --color-accent-hover--color-bg, --color-bg-odd, --color-bg-alt, --color-text--tp-dark — the translucent button surface--radius-pill, --toucharea--measure — paragraph reading width (70ch)--transition, --transition-fastTo retheme, override these variables — you rarely need to touch the rest of the sheet.
Utilities live in the last layer, so they override components and base
styles without !important. They use the spacing scale too.
.sr-only — visually hidden, still read by screen readers..center-items — flex row with vertically centred items..mb-1 — --space-sm bottom margin (15px)..mb-2 — --space-lg bottom margin (30px)..text-muted — reduced opacity..bg-odd, .bg-alt, .no-bg — background helpers.
Focus rings use :focus-visible, so mouse clicks do not show an
outline while keyboard navigation does. Touch targets are at least
44px tall through --toucharea.
Transitions are disabled under prefers-reduced-motion: reduce,
and buttons get a visible border under forced-colors: active
so they remain distinguishable in high-contrast mode.
<button> — it is styled out of the box..button only when the element is a link..accent, .lg, .icon next to it.px value.--section-padding, --gap) before a raw step.Nothing else is included yet. New components, layouts, and utilities get added to their matching layer as the project grows.
Any questions, recommendations, or comments on this stylesheet? Feel free to help me improve.