Lightweight
39.9 KB minified, 7.5 KB gzipped.
Every example below is rendered with the very stylesheet being demoed. No preprocessor. No JavaScript. No extra dependencies. Copy any snippet into a fresh HTML file and it works.
Interactive elements the visitor clicks, taps, or focuses to drive the page.
A semantic, themable action trigger. Solid and outline variants ship six brand colours each.
<a href="#" class="button primary">Primary</a>
<a href="#" class="button secondary">Secondary</a>
<a href="#" class="button tertiary">Tertiary</a>
Outline variants:
<a href="#" class="button primary-outline">Outline</a>
<a href="#" class="button secondary-outline">Outline</a>
Accessibility note. Every
.buttonvariant ships with a:focus-visiblering and a 24×24 px minimum hit area (WCAG 2.2 SC 2.5.8). Use<button type="button">for in-page actions and<a href>only for navigation.
The .btn family is a parallel shape system on top of the .button
colour family. Use it when you want a square, round, or oval icon
button instead of the default pill.
<button type="button" class="btn">Square</button>
<button type="button" class="btn btn-round">●</button>
<button type="button" class="btn btn-oval">Oval</button>
<button type="button" class="btn btn-outline">Outline</button>
When to use.
.btn-roundmakes a 60×60 px circular button — ideal for a single-character icon (close, like, share)..btn-ovalis a softer pill for short verbs ("Buy", "Send")..btn-outlinereads as a secondary action — pair it with a solid.btnfor the primary call to action.
Use .button-group to render a set of related buttons as a single
visual unit. .block makes a single button stretch full-width — handy
for forms on narrow viewports.
<div class="button-group">
<a href="#" class="button primary">Save</a>
<a href="#" class="button secondary">Save as draft</a>
<a href="#" class="button warning">Discard</a>
</div>
<a href="#" class="button primary block">Full-width call to action</a>
Accessibility note. Wrap a button group in
role="group"with anaria-labeldescribing what the buttons do together (e.g. "Document actions"). Without that, assistive tech announces each button as if it were unrelated.
A compact label for status, count, or category. Text size is fixed so badges align with surrounding text.
<span class="badge">Default</span>
<span class="badge primary">Primary</span>
<span class="badge success">Success</span>
<span class="badge warning">Warning</span>
<span class="badge danger">Danger</span>
Default Primary Success Warning Danger
Accessibility note. Badges are decorative by default. When the badge carries the only signal (e.g. an unread count), wrap it in a visually-hidden helper:
<span class="visually-hidden">3 unread messages</span>.
Surfaces that tell the visitor something happened — or is about to happen.
Status messages with semantic intent. In v2.0.0 every variant is
explicitly namespaced under .alert-{primary,secondary,info,success,warning,error}
so the variant class cannot collide with state classes elsewhere on
the page.
<div class="alert alert-primary" role="alert">
<strong>Heads up.</strong> This is a primary alert.
</div>
<div class="alert alert-success" role="status">
<strong>Saved.</strong> Your changes are persisted.
</div>
<div class="alert alert-warning" role="alert">
<strong>Careful.</strong> This action affects shared state.
</div>
<div class="alert alert-error" role="alert">
<strong>Error.</strong> Could not save the form.
</div>
Accessibility note. Use
role="alert"for messages that demand immediate attention (errors, warnings) androle="status"for non-urgent confirmations. Both expose the message to assistive tech the moment it appears.
<code>, <kbd>, <pre>, and <samp> get monospace styling and a
thick inline-start accent stripe (which flips automatically under
<html dir="rtl"> thanks to logical properties). Status modifiers
match the alert palette so callouts and code samples can speak the
same colour language.
<code class="primary">npm install @sebastienrousseau/skeletonic-stylus</code>
<code class="success">2026-04-30 — build passed</code>
<code class="warning">deprecated since v1.1.0 — removed in v2.0</code>
<code class="error">CVE-2023-44270 patched via overrides</code>
<code class="info">use `--gr-h1` to override the heading scale</code>
npm install @sebastienrousseau/skeletonic-stylus
2026-04-30 — build passed
deprecated since v1.1.0 — removed in v2.0
CVE-2023-44270 patched via overrides
use --gr-h1 to override the heading scale
To render a keyboard shortcut, use <kbd>:
Press <kbd>Ctrl</kbd>+<kbd>K</kbd> to focus the search.
Press Ctrl+K to focus the search.
RTL note. Code blocks intentionally retain
direction: ltreven on<html dir="rtl">pages — code is conventionally left-to-right. What flips is the inline-start accent stripe, so the visual anchor stays on the reading-start edge of the block.
Containers that frame and group related content.
A bordered, padded container for a single coherent unit. Pair with the
flex-N grid for responsive card walls.
<section class="row">
<article class="card flex-1">
<div class="card-content">
<h3 class="card-title">Lightweight</h3>
<p>39.9 KB minified, 7.5 KB gzipped.</p>
</div>
</article>
<article class="card flex-1">
<div class="card-content">
<h3 class="card-title">Accessible</h3>
<p>WCAG 2.2 conformant out of the box.</p>
</div>
</article>
<article class="card flex-1">
<div class="card-content">
<h3 class="card-title">Modular</h3>
<p>Cascade-layered for easy overrides.</p>
</div>
</article>
</section>
39.9 KB minified, 7.5 KB gzipped.
WCAG 2.2 conformant out of the box.
Cascade-layered for easy overrides.
Accessibility note. Wrap each card in a semantic landmark (
<article>,<section>) and start its content with a heading (<h3>). Screen-reader users can then traverse the card list as first-class navigable regions.
Plain <table> elements get bordered cells and a contrast-flipped
<thead> automatically — no class soup needed.
<table>
<thead>
<tr><th>Framework</th><th>Brotli</th><th>JS-free</th></tr>
</thead>
<tbody>
<tr><td>Skeletonic</td><td>6.7 KB</td><td>Yes</td></tr>
<tr><td>Pico CSS</td><td>10.1 KB</td><td>Yes</td></tr>
<tr><td>Bootstrap</td><td>23.0 KB</td><td>No (Popper)</td></tr>
</tbody>
</table>
| Framework | Brotli | JS-free |
|---|---|---|
| Skeletonic | 6.7 KB | Yes |
| Pico CSS | 10.1 KB | Yes |
| Bootstrap | 23.0 KB | No (Popper) |
Accessibility note. Always use
<thead>and<th>for header cells (not<td>styled to look like a header) — assistive tech announces the column header before each cell so users can navigate the data grid.
Inputs, labels, and groupings for collecting user data.
Labels, text inputs, textareas, fieldsets and legends — all sized consistently with the rest of the design system.
<form>
<label for="name">Name</label>
<input id="name" type="text" required>
<label for="email">Email</label>
<input id="email" type="email" required class="input-primary">
<label for="msg">Message</label>
<textarea id="msg" rows="4"></textarea>
<button type="submit" class="button primary">Send</button>
</form>
Accessibility note. Every input must have a programmatically associated
<label for="…">. Group related controls inside a<fieldset>with a<legend>. Mark required fields withrequired(and a visible asterisk in the label text).
Tint a single input to communicate validation state without rebuilding
the form. The status modifier classes pair with any <input type="">
that already has element styling.
<input type="text" class="input-primary" placeholder="Primary">
<input type="text" class="input-success" placeholder="Saved">
<input type="text" class="input-warning" placeholder="Check this">
<input type="email" class="input-error" value="not-an-email">
<input type="text" class="input-info" placeholder="FYI">
Accessibility note. Colour alone never carries meaning. Pair
.input-errorwitharia-invalid="true", an inline message linked viaaria-describedby, and a visible icon or text label.
<fieldset> groups related controls; <legend> names the group. Both
inherit Skeletonic's spacing and border tokens automatically.
<fieldset>
<legend>Notification preferences</legend>
<p>
<input id="email-pref" type="checkbox" checked>
<label for="email-pref">Email digest</label>
</p>
<p>
<input id="sms-pref" type="checkbox">
<label for="sms-pref">SMS alerts</label>
</p>
</fieldset>
Plain HTML, lifted to typographic standard via the cascade.
Three bullet styles ship as modifiers on <ul> — square, circle,
disc. Plus the default unstyled list-reset.
<ul class="square">
<li>Square bullets</li>
<li>Square bullets</li>
</ul>
<ul class="circle">
<li>Circle bullets</li>
<li>Circle bullets</li>
</ul>
<ul class="disc">
<li>Disc bullets</li>
<li>Disc bullets</li>
</ul>
Ten visual variants of <hr> — solid, dashed, dotted, doubled,
rounded, blurred, small, vertical, plus a centred-icon and a centred
text-on-rule used throughout this site for the section separators
above.
<hr class="hr-solid">
<hr class="hr-dashed">
<hr class="hr-dotted">
<hr class="hr-doubled">
<hr class="hr-rounded">
<hr class="hr-blurred">
<hr class="hr-small">
<hr class="hr-icon">
<hr class="hr-text" data-content="Section title">
<hr class="hr-vertical">
Solid
Dashed
Dotted
Doubled
Rounded
Blurred
Small (centred)
Centred icon
Centred text
Note.
<hr>is a semantic, paragraph-level thematic break. Don't use it for purely decorative spacing — use a<div>or CSSmargininstead. Assistive tech announces every<hr>as a section change.
Twelve named hover-effect classes for inline links, ranging from classic underline reveals to bracket animations. They're independent of colour — pair with any text colour.
<a href="#" class="link-1">Underline left → right</a>
<a href="#" class="link-2">Underline right → left</a>
<a href="#" class="link-3">Underline grow from centre</a>
<a href="#" class="link-4">Underline shrink to centre</a>
<a href="#" class="link-5">Top + bottom, left → right</a>
<a href="#" class="link-6">Top + bottom, right → left</a>
<a href="#" class="link-7">Top + bottom, grow from centre</a>
<a href="#" class="link-8">Top + bottom, opposite start</a>
<a href="#" class="link-9">Underline going up</a>
<a href="#" class="link-10">Underline going down</a>
<a href="#" class="link-11">Expanding brackets</a>
<a href="#" class="link-12">Shrinking brackets</a>
link-1 · link-2 · link-3 · link-4 · link-5 · link-6 · link-7 · link-8 · link-9 · link-10 · link-11 · link-12
RTL note. Effects 1, 2, 5, 6, 8, and 11 have explicit
[dir="rtl"]overrides so "left → right" reveals semantically become "start → end" reveals on RTL pages — meaning the underline grows from the same side the reader is already starting from.
Structural primitives — grid, container, header — that frame the whole page.
Native CSS Grid since v2.0. .grid sets display: grid and a
gap; .grid-cols-N (1–12) generates the column tracks; .col-span-N
(1–12) lets a single child span multiple cells.
<div class="grid grid-cols-2">
<div>Half</div>
<div>Half</div>
</div>
<div class="grid grid-cols-3">
<div>Third</div>
<div>Third</div>
<div>Third</div>
</div>
<div class="grid grid-cols-3">
<div class="col-span-2">Two thirds</div>
<div>Third</div>
</div>
Accessibility note. Visual order should match DOM order. Avoid reordering with
grid-auto-flow: denseor explicitgrid-row/grid-columnshuffling — screen readers and keyboard users follow the source, not the paint.
Migrating from v1.x. The
.flex-N/.rowflexbox grid is gone. Replace<div class="row"><div class="flex-6">…with<div class="grid grid-cols-2"><div>…. The new system is shorter, uses native CSS Grid, and avoids margin-collapse quirks.
A complete CSS-only header with a responsive hamburger toggle. Zero
JavaScript. Copy the snippet below into a fresh HTML page that
already loads skeletonic.min.css and the menu collapses, expands,
and traps focus correctly on its own.
<style>
/* Demo uses light-dark() so it adapts to colour-scheme. */
.sk-header{position:relative;display:flex;align-items:center;gap:1rem;padding:.75rem 1rem;border:1px solid var(--c-border);border-radius:.5rem;background:light-dark(#fff,#1a1a1a);color:light-dark(#1a1a1a,#f5f5f5);}
.sk-header .sk-brand{font-weight:600;color:light-dark(#0a0a0a,#f5f5f5) !important;text-decoration:none;}
.sk-header .sk-toggle{position:absolute;left:-9999px;}
.sk-header .sk-burger{display:none;margin-left:auto;cursor:pointer;padding:.625rem .75rem;border:1px solid var(--c-border);border-radius:.375rem;font-size:1.125rem;line-height:1;min-width:2.75rem;min-height:2.75rem;color:inherit;background:transparent;}
.sk-header .sk-burger:focus-within,.sk-header .sk-toggle:focus-visible+.sk-burger{outline:2px solid hsl(210,100%,42%);outline-offset:2px;}
.sk-header .sk-menu{list-style:none;display:flex;gap:.25rem;margin:0 0 0 auto;padding:0;}
.sk-header .sk-menu a{display:inline-flex;align-items:center;min-height:2.75rem;padding:.5rem .875rem;border-radius:.375rem;color:light-dark(#1a1a1a,#f5f5f5) !important;text-decoration:none;}
.sk-header .sk-menu a:hover{background:light-dark(#f4f4f5,#262629);}
@media (max-width:640px){
.sk-header .sk-burger{display:inline-flex;align-items:center;justify-content:center;}
.sk-header .sk-menu{display:none;flex-direction:column;gap:0;position:absolute;top:calc(100% + .25rem);left:0;right:0;background:light-dark(#fff,#1a1a1a);border:1px solid var(--c-border);border-radius:.5rem;padding:.375rem;box-shadow:0 4px 24px rgba(0,0,0,.18);z-index:10;}
.sk-header .sk-menu a{padding:.75rem 1rem;border-bottom:1px solid var(--c-border);}
.sk-header .sk-menu li:last-child a{border-bottom:0;}
.sk-header .sk-toggle:checked ~ .sk-menu{display:flex;}
}
</style>
<header class="sk-header">
<a class="sk-brand" href="#">Brand</a>
<input class="sk-toggle" id="sk-nav-toggle" type="checkbox" aria-label="Toggle navigation">
<label class="sk-burger" for="sk-nav-toggle" aria-hidden="true">☰</label>
<ul class="sk-menu">
<li><a href="#">Home</a></li>
<li><a href="#">Docs</a></li>
<li><a href="#">Components</a></li>
<li><a href="#">About</a></li>
</ul>
</header>
Accessibility note. The hidden checkbox stays in the tab order so keyboard users can open the menu with
SpaceorEnter. The<label>carriesaria-hiddenbecause the checkbox itself is the accessible name source. Resize the window below 640 px to see the burger toggle take over.
See full a11y notes → · Browse palettes → · Framework benchmarks →