CSS component library built around depth bevels, gradients, shadows, noise texture. everything is driven by CSS custom properties, so swapping themes is just changing one <link>
colors
#colorsuse variables everywhere, not hardcoded hex values. all themes define the same names, so components work across themes without changes
/* backgrounds: darkest to brightest */
--bg-void /* deepest layer, page bg */
--bg-deep /* sunken wells, code blocks */
--bg-base /* main content canvas */
--bg-raised /* elevated panels */
--bg-float /* floating elements, popovers */
--bg-hover /* hover state fills */
/* accents */
--blue-t / --blue-b / --blue-lit / --blue-dim
--secondary-t / --secondary-b / --secondary-lit / --secondary-glow
/* semantic */
--danger-t / --danger-b
--success-t / --success-b
--warn-t / --warn-b
/* raised */
background: linear-gradient(180deg, var(--surf-t) 0%, var(--surf-b) 100%);
border: 1px solid var(--border-lo);
box-shadow: var(--bevel-hi), var(--bevel-lo), var(--shadow-md);
/* sunken */
background: var(--bg-deep);
border: 1px solid var(--border-lo);
box-shadow: var(--shadow-inset);
typography
#typographyplain HTML elements get styles without any classes. headings, paragraphs, links, code, kbd, blockquote, hr, etc
Display H1
Section H2
Subsection H3
Card title H4
Label H5
Micro label H6
this is regular body text. you can put links in here, bold stuff, italics, inline code snippets, keyboard shortcuts like ⌘K, and highlighted text. all of it just works on plain HTML elements
blockquote looks like this, good for quotes or text that needs to stand out
box-shadow:
inset 0 1px 0 rgba(255,255,255,0.08),
inset 0 -1px 0 rgba(0,0,0,0.35),
0 3px 10px rgba(0,0,0,0.5);
<h1>Heading 1</h1>
<p>Body with <a>links</a>, <strong>bold</strong>, <em>italic</em>,
<code>code</code>, <kbd>Ctrl+K</kbd>, <mark>highlight</mark>.</p>
<blockquote>Quote</blockquote>
<pre><code>code block</code></pre>
panels
#panelsbasic container with the raised surface treatment: gradient, bevel lines, border, shadow. use it to group related content
panel--inset: pushed inward: good for code output or read-only data
<div class="panel">content</div>
<div class="panel panel--raised">content</div>
<div class="panel panel--inset">content</div>
<!-- with header and footer -->
<div class="panel">
<div class="panel__header">Title</div>
content
<div class="panel__footer">
<button class="btn btn--ghost">Cancel</button>
<button class="btn btn--primary">Save</button>
</div>
</div>
buttons
#buttonsall buttons share the base btn class. combine with a variant and optionally a size modifier
<button class="btn btn--primary">Primary</button>
<button class="btn btn--secondary">Secondary</button>
<button class="btn btn--ghost">Ghost</button>
<button class="btn btn--danger">Danger</button>
<button class="btn btn--success">Success</button>
<button class="btn btn--primary" disabled>Disabled</button>
<button class="btn btn--primary btn--sm">Small</button>
<button class="btn btn--primary btn--lg">Large</button>
<button class="btn btn--secondary btn--pill">Pill</button>
<button class="btn btn--ghost btn--icon">...svg...</button>
form elements
#formsinputs use --shadow-inset to look recessed. labels, hints, and error messages are separate elements. wrap them in .form-group to stack them
<div class="form-group">
<label class="form-label form-label--required">Name</label>
<input class="input" type="text" placeholder="...">
<span class="form-hint">hint text</span>
</div>
<!-- error state -->
<input class="input input--error">
<span class="form-error">message</span>
<textarea class="input" rows="4"></textarea>
<select class="select">
<option>Option A</option>
</select>
<label class="checkbox-wrap">
<input type="checkbox" checked>
<span class="checkbox-box"></span>
<span>Label text</span>
</label>
<label class="radio-wrap">
<input type="radio" name="group" checked>
<span class="radio-dot"></span>
<span>Option A</span>
</label>
<label class="toggle-wrap">
<input type="checkbox" checked>
<div class="toggle-track"><div class="toggle-thumb"></div></div>
<span>Label</span>
</label>
<input class="range" type="range" min="0" max="100" value="70">
badges & tags
#badgesnadges are small colored labels, usually for status or counts. tags are the lower-contrast version, good for categorization and filters
<span class="badge badge--primary">Stable</span>
<span class="badge badge--secondary">Pro</span>
<span class="badge badge--success">Live</span>
<span class="badge badge--danger">Breaking</span>
<span class="badge badge--warn">Beta</span>
<span class="badge badge--muted">Archived</span>
<span class="tag">default</span>
<span class="tag tag--blue">feature</span>
<span class="tag tag--secondary">enhancement</span>
<span class="tag tag--red">critical</span>
<span class="tag tag--green">merged</span>
alerts
#alertsfull-width banners for feedback. each variant maps to colored role
<div class="alert alert--info">...</div>
<div class="alert alert--success">...</div>
<div class="alert alert--warn">...</div>
<div class="alert alert--danger">...</div>
tabs
#tabsuses Alpine.js x-data for active state. the active tab receives tab--active, no other JS required
<div x-data="{ tab: 'a' }">
<div class="tabs">
<button class="tab"
:class="tab === 'a' && 'tab--active'"
@click="tab='a'">Tab A</button>
</div>
<div x-show="tab === 'a'">Content A</div>
</div>
list items
#listused inside sidebars and navigation panels. list-section provides a muted group label, list-item--active highlights the selected row
<div class="list-section">Group Label</div>
<div class="list-item list-item--active">Active item</div>
<div class="list-item">Normal item</div>
progress
#progressinset track with a filled bar. set the bar width inline as a percentage, alternate fill colors: progress__fill--gold and progress__fill--success
<div class="progress">
<div class="progress__fill" style="width: 72%"></div>
</div>
<!-- color variants -->
progress__fill--secondary
progress__fill--success
avatars
#avatarscircular (or square) initials/image containers. wrap with avatar-wrap to attach a status dot
<!-- sizes: avatar--sm avatar avatar--lg avatar--xl -->
<!-- shapes: default (circle), avatar--square -->
<!-- status: --online --idle --dnd --offline -->
<div class="avatar-wrap">
<div class="avatar" style="background:...">AB</div>
<span class="avatar-status avatar-status--online"></span>
</div>
tooltip
#tooltipp tooltip, no js. the tooltip appears above the trigger element on hover
<span data-tooltip="Tooltip content">Hover me</span>
<button class="btn" data-tooltip="Save changes (⌘S)">save</button>
modal
#modaluses Alpine.js for open/close. the overlay receives is-open class to display, clicking the backdrop closes it
<button class="btn btn--primary" @click="modalOpen = true">Open</button>
<div class="overlay"
:class="modalOpen && 'is-open'"
@click.self="modalOpen = false">
<div class="modal">
<div class="modal__header">
<div class="modal__title">Title</div>
<button class="btn btn--ghost btn--icon btn--sm"
@click="modalOpen = false">✕</button>
</div>
<div class="modal__body">content</div>
<div class="modal__footer">
<button class="btn btn--ghost">Cancel</button>
<button class="btn btn--primary">Confirm</button>
</div>
</div>
</div>
table
#tablewrap <table> in .table-wrap for horizontal scroll and consistent border/shadow. standard thead/tbody structure is all that's needed
| Project | Status | Language | Updated | Actions |
|---|---|---|---|---|
RD renderer |
Live | C++ | 2 hours ago | |
UI ui |
Beta | JS | Yesterday | |
CR core |
Archived | Go | 3 months ago |
<div class="table-wrap">
<table>
<thead><tr>
<th>Name</th>
<th>Status</th>
</tr></thead>
<tbody><tr>
<td>renderer</td>
<td><span class="badge badge--success">Live</span></td>
</tr></tbody>
</table>
</div>
toast / snackbar
#toasttemporary notifications that render inside .toast-region (fixed, bottom-right), left-border color maps to notification type
<div class="toast-region">
<div class="toast toast--success">
<div class="toast__body">
<div class="toast__title">Saved</div>
Changes pushed to main.
</div>
</div>
</div>
<!-- variants: toast--info toast--success toast--warn toast--danger -->
accordion
#accordioncollapsible sections with a beveled trigger button and smooth max-height transition. toggle .accordion__item--open to expand
<link> href to point at a different theme file, each theme just overrides the same CSS variables at :root.--surf-t, --bevel-hi, --shadow-md, etc) and your stuff will pick up whatever theme is active<div class="accordion">
<div class="accordion__item accordion__item--open">
<button class="accordion__trigger">
Title <span class="accordion__chevron"></span>
</button>
<div class="accordion__body">
<div class="accordion__content">Content</div>
</div>
</div>
</div>
<!-- toggle .accordion__item--open to expand/collapse -->
drawer
#drawerslide-in panel from the right (or left with .drawer--left). pair with .drawer-overlay for the backdrop. toggle --open modifiers to show
<div class="drawer-overlay drawer-overlay--open" onclick="closeDrawer()"></div>
<div class="drawer drawer--open">
<div class="drawer__header">
<span class="drawer__title">Settings</span>
<button class="btn btn--ghost btn--icon btn--sm">✕</button>
</div>
<div class="drawer__body">content</div>
</div>
<!-- left variant: add .drawer--left -->
popover
#popoverlike a tooltip but for more content: a title, text, links. use it for inline help or previews, add .popover--visible to show it, .popover--right to anchor it to the right instead
Status: ● live
<div class="popover-anchor">
<button>trigger</button>
<div class="popover popover--visible">
<div class="popover__title">Title</div>
Popover body text.
</div>
</div>
<!-- right variant: .popover--right -->
number stepper
#stepper+/− buttons on either side of a number input. good for quantity fields or config values where a spinner feels too plain
<div class="stepper">
<button class="stepper__btn">−</button>
<input class="stepper__input" type="number" value="4">
<button class="stepper__btn">+</button>
</div>
file drop zone
#dropzonedashed-border inset surface for drag-and-drop uploads, hover and active (dragging) states change the border and background
<div class="dropzone">
<span class="dropzone__label">drop files or <a>browse</a></span>
<span class="dropzone__hint">PNG, JPG — max 20 MB</span>
</div>
<!-- add .dropzone--hover on hover, .dropzone--active while dragging -->
chip input
#chipinputtag/multi-value input. chips live inside the field; pressing enter or comma adds a new one. remove with the X button
<div class="chip-input">
<span class="chip chip--blue">
python
<button class="chip__remove">✕</button>
</span>
<input class="chip-input__field" placeholder="add tag…">
</div>
<!-- chip variants: chip--blue chip--gold -->
color swatch
#swatchpressable color dot with a bevel highlight, .swatch--selected shows a focus indicator
<div class="swatch-row">
<div class="swatch swatch--selected" style="background:#3b5f96"></div>
<div class="swatch" style="background:#a07820"></div>
</div>
stat card
#statcardbig number + label + optional trend indicator, useful for dashboards and summary views
<div class="stat-card">
<div class="stat-card__label">requests / day</div>
<div class="stat-card__value">2.4M</div>
<div class="stat-card__trend stat-card__trend--up">▲ 12.3%</div>
<div class="stat-card__sublabel">vs last 7 days</div>
</div>
<!-- trend variants: stat-card__trend--up --down --flat -->
skeleton loader
#skeletonshimmer placeholders for async content, size with inline width/height or use the shape variants
<div class="skeleton skeleton--avatar" style="width:2.5rem;height:2.5rem"></div>
<div class="skeleton skeleton--title" style="width:55%"></div>
<div class="skeleton skeleton--text" style="width:100%"></div>
<div class="skeleton skeleton--btn" style="width:90px"></div>
<div class="skeleton skeleton--card" style="width:100%"></div>
timeline
#timelinevertical event list with connector line runs through .timeline__spine, the last item hides it automatically
<div class="timeline">
<div class="timeline__item">
<div class="timeline__spine">
<div class="timeline__dot"></div>
<div class="timeline__line"></div>
</div>
<div class="timeline__body">
<div class="timeline__header">
<span class="timeline__title">Event</span>
<span class="timeline__time">2 min ago</span>
</div>
<div class="timeline__text">description</div>
</div>
</div>
</div>
<!-- muted dot: .timeline__dot--muted -->
avatar group
#avatargroupoverlapping avatars with a border gap, the .avatar-group__overflow pill shows the excess count
<div class="avatar-group">
<div class="avatar avatar--md">MK</div>
<div class="avatar avatar--md">AV</div>
<div class="avatar avatar--md avatar-group__overflow">+4</div>
</div>
tree view
#treeviewnested file/folder list with chevron toggles and a border-left connector
toggle .tree-node--open to expand
<div class="tree">
<div class="tree-node tree-node--open">
<div class="tree-node__row">
<span class="tree-node__chevron"></span>
src/
</div>
<div class="tree-node__children">
<div class="tree-node tree-node--leaf">
<div class="tree-node__row">
<span class="tree-node__chevron"></span>
index.ts
</div>
</div>
</div>
</div>
</div>
<!-- toggle .tree-node--open to expand. leaf nodes: .tree-node--leaf -->
empty state
#emptystateplaceholder for when there's nothing to show yet: icon box, title, description, and an optional action button
<div class="empty-state">
<div class="empty-state__icon">
<!-- svg icon -->
</div>
<div class="empty-state__title">No items yet</div>
<div class="empty-state__text">Descriptive helper copy.</div>
<button class="btn btn--primary btn--sm">create</button>
</div>
spoiler
#spoilerblur-hidden inline text, revealed on click. common in chat UIs for sensitive content or spoilers
radagon is marika
your API key: xK9mP2nQr8vTsWz
click the blurred text to reveal
The answer is
<span class="spoiler"
onclick="this.classList.toggle('spoiler--revealed')">
hidden text here
</span>