css library
skui

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>

<link rel="stylesheet" href="https://skui.pages.dev/skui.css" >

colors

#colors

use variables everywhere, not hardcoded hex values. all themes define the same names, so components work across themes without changes

backgrounds
void
deep
base
raised
float
hover
primary
secondary
danger
success
warn
css variables
/* 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
surface recipe: three layers together
raised surface gradient + bevel + border
sunken surface --shadow-inset
css
/* 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

#typography

plain 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);
html
<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

#panels

basic container with the raised surface treatment: gradient, bevel lines, border, shadow. use it to group related content

default panel

default panel: raised gradient with bevel lines on the edges

panel--raised

more lift: use this for dropdowns, popovers, floating things

panel--inset: pushed inward: good for code output or read-only data

with header & footer

panel content area

html
<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

#buttons

all buttons share the base btn class. combine with a variant and optionally a size modifier

variants
html
<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>
sizes & shapes
html
<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

#forms

inputs use --shadow-inset to look recessed. labels, hints, and error messages are separate elements. wrap them in .form-group to stack them

text inputs
shown publicly on your profile
enter a valid email address
html
<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>
checkboxes
html
<label class="checkbox-wrap">
  <input type="checkbox" checked>
  <span class="checkbox-box"></span>
  <span>Label text</span>
</label>
radio buttons
html
<label class="radio-wrap">
  <input type="radio" name="group" checked>
  <span class="radio-dot"></span>
  <span>Option A</span>
</label>
toggles & range
html
<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

#badges

nadges are small colored labels, usually for status or counts. tags are the lower-contrast version, good for categorization and filters

badges
Stable Pro Live Breaking Beta Archived
html
<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>
tags
default feature enhancement critical merged
html
<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

#alerts

full-width banners for feedback. each variant maps to colored role

Information: changes take effect on the next request
Success: changes have been saved
Warning: 84% of your hourly quota has been used
Error: something happened
html
<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

#tabs

uses Alpine.js x-data for active state. the active tab receives tab--active, no other JS required

Project is healthy. Latest release reduced bundle size by 18%.
Past 30 days: 142,800 API calls, avg response 48ms.
Configure rate limits, webhooks, and access tokens.
html (requires alpine.js)
<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

#list

used inside sidebars and navigation panels. list-section provides a muted group label, list-item--active highlights the selected row

Library
All Tracks
Albums
Artists
Playlists
Late Night Study
Morning Run
html
<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

#progress

inset track with a filled bar. set the bar width inline as a percentage, alternate fill colors: progress__fill--gold and progress__fill--success

html
<div class="progress">
  <div class="progress__fill" style="width: 72%"></div>
</div>

<!-- color variants -->
progress__fill--secondary
progress__fill--success

avatars

#avatars

circular (or square) initials/image containers. wrap with avatar-wrap to attach a status dot

AK
RH
MV
SL
html
<!-- 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

#tooltip

p tooltip, no js. the tooltip appears above the trigger element on hover

hover me stable
html
<span data-tooltip="Tooltip content">Hover me</span>

<button class="btn" data-tooltip="Save changes (⌘S)">save</button>

table

#table

wrap <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
html
<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

#toast

temporary notifications that render inside .toast-region (fixed, bottom-right), left-border color maps to notification type

deployment started
project is building…
saved
changes pushed to main
rate limit
429: too many requests
build failed
error in routes/api.py:42
html
<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

#accordion

collapsible sections with a beveled trigger button and smooth max-height transition. toggle .accordion__item--open to expand

it's a design style where UI elements look like physical objects. gradients, shadows, bevels, and textures that make things look like they have actual depth
change the <link> href to point at a different theme file, each theme just overrides the same CSS variables at :root.
just use the same CSS variables (--surf-t, --bevel-hi, --shadow-md, etc) and your stuff will pick up whatever theme is active
html
<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

#drawer

slide-in panel from the right (or left with .drawer--left). pair with .drawer-overlay for the backdrop. toggle --open modifiers to show

html
<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

#popover

like 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

renderer
Last deployed 3 minutes ago by @dev.
Status: ● live
html
<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

html
<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

#dropzone

dashed-border inset surface for drag-and-drop uploads, hover and active (dragging) states change the border and background

drop files here, or browse PNG, JPG, PDF, max 20 MB
html
<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

#chipinput

tag/multi-value input. chips live inside the field; pressing enter or comma adds a new one. remove with the X button

python js css
python css-library css dark-mode
html
<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

#swatch

pressable color dot with a bevel highlight, .swatch--selected shows a focus indicator

small variant
html
<div class="swatch-row">
  <div class="swatch swatch--selected" style="background:#3b5f96"></div>
  <div class="swatch" style="background:#a07820"></div>
</div>

stat card

#statcard

big number + label + optional trend indicator, useful for dashboards and summary views

requests / day
2.4M
▲ 12.3%
vs last 7 days
error rate
0.07%
▼ 0.02%
p99 latency: 48ms
active users
18,342
no change
last 30 days
html
<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

#skeleton

shimmer placeholders for async content, size with inline width/height or use the shape variants

html
<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

#timeline

vertical event list with connector line runs through .timeline__spine, the last item hides it automatically

deployed to production 2 min ago
v2.4.1, fixes rate-limits on /api/events
PR merged 18 min ago
feat: add stepper and accordion components
build started 20 min ago
CI triggered on push to main
Branch created 1 hr ago
feat/new-components off main
html
<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

#avatargroup

overlapping avatars with a border gap, the .avatar-group__overflow pill shows the excess count

MK
AV
RZ
+4
M
A
R
J
+2
html
<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

#treeview

nested file/folder list with chevron toggles and a border-left connector

toggle .tree-node--open to expand

src
misc
module2.py
module.py
main.py
config.yml
html
<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

#emptystate

placeholder for when there's nothing to show yet: icon box, title, description, and an optional action button

no projects yet
you don't have any projects yet, create one to get started.
html
<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

#spoiler

blur-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

html
The answer is
<span class="spoiler"
      onclick="this.classList.toggle('spoiler--revealed')">
  hidden text here
</span>
Settings
Navigation
dashboard
projects
deployments
settings