TinyWire is a design system for the interfaces where people operate complex software: dashboards, data tables, and control surfaces. Most of my client work is under NDA, so this is where I show how I approach operator UI. I build prototypes on it and fold what I learn back in.
Live demo & docs → · License: MIT · No build step
- 2 fonts: Bricolage Grotesque (display) + DM Sans (body)
- ~180 tokens, three tiers (primitive → semantic → component): colors, type, spacing, radius, elevation, animation — all light & dark
- 28 components: forms, feedback, navigation, overlays, data
- 5 patterns: dashboard, data table, settings, login, empty states
- Built-in WCAG checker:
docs/a11y.htmlcomputes live contrast in both themes - No framework, no build step: drop two CSS files in and go
lib/
├── globals.css Tokens (light + dark) + reset + base + keyframes
├── components.css All 28 components
└── tokens.js Same tokens as a JS object (for scripts/generators)
docs/
├── index.html Intro + quick start
├── foundations.html Token reference
├── components.html Component library
├── patterns.html Composed patterns
├── docs.css Docs-only styles
└── docs.js Sidebar, dark mode, code reveal
- Add the fonts to your
<head>:
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:wght@400..700&family=DM+Sans:wght@400..600&display=swap" rel="stylesheet">- Link the stylesheets:
<link rel="stylesheet" href="lib/globals.css">
<link rel="stylesheet" href="lib/components.css">- Use the components:
<button class="btn btn-primary">Apply changes</button>
<div class="card">
<div class="card-title">Section title</div>
<p class="card-desc">Card content.</p>
</div>- Enable dark mode:
document.documentElement.setAttribute('data-theme', 'dark');| Prefix | Purpose |
|---|---|
.btn-* |
Buttons (.btn-primary, .btn-ghost, etc.) |
.input |
Text input |
.select |
Select dropdown |
.checkbox |
Checkbox |
.radio |
Radio |
.switch |
Toggle switch |
.slider |
Range slider |
.card |
Container |
.tag-* |
Loud, all-caps tags |
.badge |
Quiet metadata |
.chip-* |
Status pills |
.dot-* |
Status indicators |
.alert-* |
Inline messages |
.banner-* |
Full-width banners |
.dialog |
Modal |
.sheet |
Side drawer |
.tooltip |
Tooltip |
.popover |
Click popover |
.menu |
Dropdown menu |
.tabs-list |
Tabs |
.accordion |
Accordion |
.breadcrumb |
Breadcrumb |
.pagination |
Pagination |
.toast |
Toast notification |
.command |
Command palette (⌘K) |
.table |
Data table |
.sidebar |
App sidebar (.sidebar--rail for the collapsed icon rail) |
.empty-state |
Empty state placeholder |
Every component reads from CSS custom properties. To rebrand, override the tokens — never touch the component CSS.
/* Override at any scope */
.brand-acme {
--brand: #0066CC;
--brand-fg: #FFFFFF;
--brand-light: #E6F0FB;
--brand-dark: #003D7A;
}Contributions welcome — new components, variants, fixes, accessibility improvements. The one hard rule: components read tokens, never hardcoded values (a CI check enforces it). See CONTRIBUTING.md for the workflow and CODE_OF_CONDUCT.md for community expectations.
MIT © 2026 Lindsay Zuniga (@LinzLos). Use it, fork it, ship it.
