Svelte Tailwind UI Components
An opinionated Svelte 5 component library built with Tailwind CSS v4. Featuring a centralized design token system for consistent theming across all components.
npm install @marianmeres/stuic
<script>
import { Button, Modal } from "@marianmeres/stuic";
let modal;
</script>
<Button onclick={() => modal.open()}>Open Modal</Button>
<Modal bind:this={modal}>
<p>Hello from Modal!</p>
</Modal>
STUIC uses a 3-layer CSS variable token system that enables both global theming and per-component customization.
Layer 1: Global Semantic Tokens (--stuic-accent, --stuic-surface, etc.)
↓ (used as fallback defaults)
Layer 2: Component Tokens (--stuic-button-bg, --stuic-input-accent, etc.)
↓ (Tailwind utility class references)
Layer 3: Instance Overrides (inline styles, class props)
Override global tokens in your app's CSS to change the entire library's appearance:
/* app.css */
:root {
/* Change the accent color globally */
--stuic-accent: #6366f1; /* Indigo brand color */
--stuic-accent-hover: #4f46e5;
--stuic-accent-active: #4338ca;
}
.dark {
--stuic-accent: #818cf8;
--stuic-accent-hover: #a5b4fc;
}
Override specific component tokens:
:root {
/* Only change switches to green */
--stuic-switch-accent: #10b981;
/* Custom button colors */
--stuic-button-bg: #f3f4f6;
--stuic-button-bg-hover: #e5e7eb;
}
Use class props or inline styles for one-off customizations:
<Button class="bg-purple-500 hover:bg-purple-600 text-white">
Custom Button
</Button>
<div style="--stuic-list-item-button-bg-hover: var(--color-blue-500);">
<ListItemButton>Blue hover</ListItemButton>
</div>
All tokens are defined in src/lib/theme.css. See the full reference below.
| Token | Light Mode | Dark Mode | Description |
|---|---|---|---|
--stuic-accent |
sky-600 |
sky-400 |
Primary accent for interactive elements |
--stuic-accent-hover |
sky-700 |
sky-300 |
Accent hover state |
--stuic-accent-active |
sky-800 |
sky-200 |
Accent active/pressed state |
--stuic-accent-destructive |
red-600 |
red-400 |
Destructive/error accent |
--stuic-accent-destructive-hover |
red-700 |
red-300 |
Destructive hover state |
| Token | Light Mode | Dark Mode | Description |
|---|---|---|---|
--stuic-surface |
white |
neutral-900 |
Base page background |
--stuic-surface-elevated |
white |
neutral-800 |
Cards, modals, popovers |
--stuic-surface-sunken |
neutral-100 |
neutral-700 |
Input backgrounds, wells |
--stuic-surface-overlay |
neutral-800 |
neutral-950 |
Backdrops, tooltips |
--stuic-surface-interactive |
neutral-200 |
neutral-600 |
Buttons, list items |
--stuic-surface-interactive-hover |
neutral-500 |
neutral-200 |
Interactive hover |
--stuic-surface-interactive-active |
neutral-600 |
neutral-100 |
Interactive active |
| Token | Light Mode | Dark Mode | Description |
|---|---|---|---|
--stuic-text |
black |
neutral-100 |
Primary text |
--stuic-text-muted |
neutral-600 |
neutral-400 |
Secondary/muted text |
--stuic-text-inverse |
white |
neutral-900 |
Text on dark backgrounds |
--stuic-text-placeholder |
neutral-400 |
neutral-500 |
Placeholder text |
--stuic-text-on-accent |
white |
neutral-950 |
Text on accent backgrounds |
--stuic-text-destructive |
red-600 |
red-400 |
Error/destructive text |
| Token | Light Mode | Dark Mode | Description |
|---|---|---|---|
--stuic-border |
neutral-300 |
neutral-600 |
Default border |
--stuic-border-strong |
neutral-400 |
neutral-500 |
Emphasized border |
--stuic-border-subtle |
neutral-200 |
neutral-700 |
Subtle/light border |
--stuic-border-focus |
sky-500 |
sky-400 |
Focus ring border |
--stuic-border-error |
red-500 |
red-400 |
Error state border |
| Token | Default | Description |
|---|---|---|
--stuic-ring |
sky-500 / sky-400 |
Focus ring color |
--stuic-ring-offset |
2px |
Focus ring offset |
--stuic-ring-width |
2px |
Focus ring width |
--stuic-radius-sm |
--radius-sm |
Small border radius |
--stuic-radius |
--radius-md |
Default border radius |
--stuic-radius-lg |
--radius-lg |
Large border radius |
--stuic-radius-full |
9999px |
Fully rounded |
--stuic-transition-fast |
100ms |
Fast transitions |
--stuic-transition-normal |
150ms |
Normal transitions |
--stuic-transition-slow |
300ms |
Slow transitions |
Each component defines its own tokens that reference global tokens as defaults:
| Component | Token Prefix | Key Tokens |
|---|---|---|
| Button | --stuic-button-* |
bg, text, border, border-focus |
| Switch | --stuic-switch-* |
accent |
| Input | --stuic-input-* |
accent, accent-error |
| Progress | --stuic-progress-* |
bg, accent |
| ListItemButton | --stuic-list-item-button-* |
bg, text, border, bg-hover, text-hover, etc. |
| ButtonGroupRadio | --stuic-button-group-* |
bg, text, border, accent, bg-active, text-active |
| TabbedMenu | --stuic-tabbed-menu-* |
tab-bg, tab-text, tab-bg-active, tab-text-active, border |
| DismissibleMessage | --stuic-dismissible-message-* |
bg, text, border |
| Notifications | --stuic-notification-* |
bg, text, border |
| Tooltip | --stuic-tooltip-* |
bg, text |
| Popover | --stuic-popover-* |
bg, text, border |
| Skeleton | --stuic-skeleton-* |
bg, bg-highlight, duration |
STRICT REQUIREMENT: All CSS variables follow this pattern:
--stuic-{component}-{element?}-{property}-{state?}
list-item-button not lib--stuic-button-bg-hover not --stuic-button-hover-bg-dark suffix: Dark mode defined in .dark {} selectorbg, text, border, ring, shadow, accenthover, active, focus, disabled, error/* Correct */
--stuic-button-bg
--stuic-button-bg-hover
--stuic-list-item-button-text-active
--stuic-input-accent-error
/* Incorrect */
--stuic-btn-bg /* abbreviated component name */
--stuic-button-hover-bg /* state not at end */
--stuic-button-bg-dark /* -dark suffix */
--color-lib-hover-bg /* old naming convention */
Set variables in your CSS for theming:
:root {
--stuic-accent: #6366f1;
}
Pass Tailwind classes directly to components:
<Button class="bg-linear-to-r from-purple-500 to-pink-500">
Gradient Button
</Button>
Use unstyled prop to remove all default styling:
<Button unstyled class="my-custom-button-class">
Fully Custom
</Button>
See src/lib/README.md for the full component list and API documentation.
<input use:autogrow use:validate={{ required: true }} />
<button use:tooltip aria-label="Tooltip text">Hover me</button>
<div use:popover={{ content: 'Popover content' }}>Anchor</div>
autogrow - Auto-expand textarea heightvalidate - Form validation with custom validatorsfocusTrap - Trap focus within elementtooltip - Tooltip from aria-labelpopover - Anchored popoverfileDropzone - Drag-and-drop file uploadhighlightDragover - Visual feedback for drag operationsAll components export their Props types:
import type { ButtonProps, ModalProps, ListItemButtonProps } from "@marianmeres/stuic";
--color-* to --stuic-* prefix--color-lib-* to --stuic-list-item-button-*--*-hover-bg to --*-bg-hover (state at end)-dark suffix from variables (use .dark {} selector instead)MIT