@arc-lang/arc-ui
v2.4.1
Published
Liquid glass UI component library — 49 components, CSS-first, arc-animations integration. Zero dependencies.
Maintainers
Keywords
Readme
arc-ui
Liquid glass UI component library. 17 components, CSS-first, zero dependencies. Configurable via CSS custom properties — change 6 vars to retheme everything.
Part of Arc — the zero-dependency web framework.
vs. alternatives: | Library | Size | Framework | Deps | |---|---|---|---| | Headless UI | ~8KB | React only | React | | Radix UI | ~20KB+ | React only | React | | Floating UI | ~10KB | any | none | | arc-ui | ~3KB | any | none |
Install
npm install arc-uiCDN:
<link rel="stylesheet" href="https://unpkg.com/arc-ui/index.css">Glass Design System
arc-ui v2.0 introduces a single configurable token system. Every component inherits from these vars — change them once, retheme everything.
:root {
/* Glass surface */
--glass-bg: rgba(255, 255, 255, 0.10);
--glass-bg-hover: rgba(255, 255, 255, 0.16);
--glass-blur: 20px;
--glass-saturate: 180%;
--glass-border: rgba(255, 255, 255, 0.25);
--glass-shadow: 0 8px 32px rgba(0,0,0,0.10), inset 0 1px 0 rgba(255,255,255,0.18);
/* Accent */
--ui-accent: #5956f0;
/* Timing */
--ui-duration: var(--anim-duration, 0.2s); /* auto-inherits arc-animations */
--ui-easing: var(--anim-easing, cubic-bezier(0.16, 1, 0.3, 1));
}arc-animations integration
Three tiers — all CSS, no JS:
Tier 1 — Timing inheritance (automatic)
Load arc-animations before arc-ui. --ui-duration picks up --anim-duration automatically via CSS var fallback.
Tier 2 — Class composability
<div class="card animate-float-in">...</div>Tier 3 — Animation override vars
:root {
--modal-enter-animation: zoom-in; /* any arc-animations keyframe */
--toast-enter-animation: slide-right;
}Components
Pure CSS (no JS)
| Component | File | Description |
|---|---|---|
| Accordion | src/accordion.css | <details>/<summary>, height animation |
| Tooltip | src/tooltip.css | data-tooltip attribute, 4 positions |
| Switch | src/switch.css | <input type="checkbox"> styled |
| Progress | src/progress.css | --progress-value CSS var, circular SVG |
| Skeleton | src/skeleton.css | Shimmer animation, prebuilt shapes |
| Badge | src/badge.css | Color variants, dot indicator |
| Alert | src/alert.css | 4 semantic variants |
| Spinner | src/spinner.css | Ring, dots, pulse variants |
| Avatar | src/avatar.css | Image or initials, stack group |
| Card | src/card.css | Glass surface, image/body/footer |
Requires small JS (~300B each)
| Component | CSS | JS | Description |
|---|---|---|---|
| Button | src/button.css | — | All variants (loading state via CSS) |
| Modal | src/modal.css | js/modal.js | Native <dialog>, focus trap |
| Toast | src/toast.css | js/toast.js | Toast.show() API |
| Tabs | src/tabs.css | js/tabs.js | ARIA tabs, keyboard nav |
| Dropdown | src/dropdown.css | js/dropdown.js | Toggle, keyboard nav, ui:select event |
| Drawer | src/drawer.css | js/drawer.js | Slide panel, focus trap, Escape |
| Input | src/input.css | — | input/textarea/select, icon addon |
Button
<button class="btn">Default</button>
<button class="btn btn--primary">Primary</button>
<button class="btn btn--ghost">Ghost</button>
<button class="btn btn--outline">Outline</button>
<button class="btn btn--danger">Danger</button>
<button class="btn btn--sm">Small</button>
<button class="btn btn--lg">Large</button>
<button class="btn is-loading">Loading</button>| CSS property | Default | Description |
|---|---|---|
| --btn-radius | var(--ui-radius) | Border radius |
| --btn-padding | 9px 18px | Padding |
| --btn-font-size | 14px | Font size |
Modal
Native <dialog> element — backdrop, Escape key, and browser accessibility built in.
<dialog class="modal" id="my-modal">
<div class="modal__box">
<button class="modal__close" data-modal-close aria-label="Close">✕</button>
<div class="modal__header">
<h2 class="modal__title">Confirm</h2>
<p class="modal__subtitle">Are you sure?</p>
</div>
<div class="modal__footer">
<button class="btn btn--ghost" data-modal-close>Cancel</button>
<button class="btn btn--primary" data-modal-close>Confirm</button>
</div>
</div>
</dialog>
<button data-modal-open="my-modal">Open</button>
<script src="https://unpkg.com/arc-ui/js/modal.js"></script>| CSS property | Default | Description |
|---|---|---|
| --modal-width | 540px | Dialog width |
| --modal-radius | var(--ui-radius-xl) | Border radius |
| --modal-bg | rgba(255,255,255,0.88) | Glass background |
| --modal-blur | 40px | Backdrop blur |
| --modal-enter-animation | modal-in | Entrance keyframe name |
Toast
Toast.show('Saved!', { type: 'success', icon: '✓' })
Toast.show('Error occurred', { type: 'error', duration: 0 }) // persistent
Toast.show('Warning', { type: 'warning' })
Toast.show('Info message', { type: 'info' })<script src="https://unpkg.com/arc-ui/js/toast.js"></script>| Option | Default | Description |
|---|---|---|
| type | '' | success / error / warning / info |
| duration | 4000 | Auto-dismiss in ms. 0 = persistent |
| icon | '' | Icon string prepended to message |
| CSS property | Default |
|---|---|
| --toast-enter-animation | toast-in |
| --toast-bg | rgba(20,24,36,0.88) |
| --toast-radius | var(--ui-radius-lg) |
Dropdown
<details class="dropdown">
<summary class="btn dropdown__trigger">Options ▾</summary>
<div class="dropdown__menu" role="menu">
<div class="dropdown__label">Actions</div>
<button class="dropdown__item" data-value="edit">
<span class="dropdown__icon">✏</span> Edit
<span class="dropdown__shortcut">⌘E</span>
</button>
<div class="dropdown__separator"></div>
<button class="dropdown__item dropdown__item--danger" data-value="delete">Delete</button>
</div>
</details>
<script src="https://unpkg.com/arc-ui/js/dropdown.js"></script>Uses native <details> — click-outside and Escape close for free (no JS). JS adds keyboard nav (↑/↓/Tab) and dispatches ui:select CustomEvent: { value, label, item }.
Variants: .dropdown--right, .dropdown--up.
Drawer
<button data-drawer-open="my-drawer">Open</button>
<div class="drawer-overlay" data-for="my-drawer"></div>
<aside class="drawer" id="my-drawer" aria-hidden="true">
<div class="drawer__header">
<h2 class="drawer__title">Title</h2>
<button class="drawer__close" data-drawer-close>✕</button>
</div>
<div class="drawer__body">Content</div>
<div class="drawer__footer">
<button class="btn btn--ghost" data-drawer-close>Cancel</button>
<button class="btn btn--primary">Save</button>
</div>
</aside>
<script src="https://unpkg.com/arc-ui/js/drawer.js"></script>Variants: .drawer--left, .drawer--bottom (bottom sheet). Bottom sheet gets .drawer__handle (drag indicator).
Fires ui:drawer-open / ui:drawer-close CustomEvents.
| CSS property | Default |
|---|---|
| --drawer-width | 320px |
| --drawer-bg | rgba(255,255,255,0.88) |
| --drawer-blur | 32px |
| --drawer-duration | var(--ui-duration-slow) |
Input
<div class="input-group">
<label class="input-label" for="email">Email</label>
<div class="input-wrap">
<span class="input-icon">✉</span>
<input class="input" id="email" type="email" placeholder="[email protected]">
</div>
<span class="input-hint">We'll never share this.</span>
</div>
<!-- Error state -->
<div class="input-group is-invalid">
<label class="input-label">Password</label>
<input class="input" type="password">
<span class="input-hint">Must be at least 8 characters</span>
</div>
<select class="select">...</select>
<textarea class="textarea" rows="4"></textarea>Switch
<label class="switch-label">
<input type="checkbox" class="switch" checked>
Enable notifications
</label>Sizes: .switch--sm, .switch--lg. Zero JS.
Progress
<!-- Linear — set --progress-value (0–100) -->
<div class="progress" style="--progress-value:72">
<div class="progress__bar"></div>
</div>
<!-- Indeterminate -->
<div class="progress progress--indeterminate">
<div class="progress__bar"></div>
</div>
<!-- Circular SVG ring -->
<div class="progress-ring" style="--progress-value:65">
<svg viewBox="0 0 56 56" class="progress-ring__svg">
<circle class="progress-ring__track" cx="28" cy="28" r="24"/>
<circle class="progress-ring__fill" cx="28" cy="28" r="24"/>
</svg>
<span class="progress-ring__label">65%</span>
</div>Zero JS — --progress-value drives width/stroke-dashoffset via CSS calc.
Avatar
<!-- Initials -->
<span class="avatar">AB</span>
<span class="avatar avatar--lg avatar--purple">GH</span>
<!-- Image -->
<span class="avatar"><img src="photo.jpg" alt="Jane"></span>
<!-- With status -->
<div class="avatar-wrap">
<span class="avatar avatar--lg">JD</span>
<span class="avatar-status avatar-status--online"></span>
</div>
<!-- Stack group -->
<div class="avatar-stack">
<span class="avatar avatar--sm">AB</span>
<span class="avatar avatar--sm avatar--purple">CD</span>
<span class="avatar avatar--sm avatar--pink">EF</span>
</div>Sizes: --xs (24px), --sm (32px), default (40px), --lg (56px), --xl (80px).
Color presets: --purple, --pink, --orange, --teal.
Tabs
<div class="tabs">
<div class="tabs__list" role="tablist">
<button class="tabs__tab is-active" role="tab" aria-selected="true" data-tab="panel-1">Tab 1</button>
<button class="tabs__tab" role="tab" aria-selected="false" data-tab="panel-2">Tab 2</button>
</div>
<div class="tabs__panel is-active" id="panel-1" role="tabpanel">Content 1</div>
<div class="tabs__panel" id="panel-2" role="tabpanel">Content 2</div>
</div>
<script src="https://unpkg.com/arc-ui/js/tabs.js"></script>Variants: .tabs--pills (glass pill), .tabs--boxed.
Accordion
<div class="accordion">
<details class="accordion__item">
<summary class="accordion__trigger">Question?</summary>
<div class="accordion__content">Answer.</div>
</details>
</div>Zero JS. Height animates in Chrome 129+ via interpolate-size: allow-keywords. Add .accordion--flush for separator-only style.
Alert
<div class="alert alert--success">
<span class="alert__icon">✓</span>
<div>
<div class="alert__title">Success</div>
<div class="alert__text">Changes saved.</div>
</div>
</div>Variants: alert--info, alert--success, alert--warning, alert--error.
Skeleton
<div class="skeleton skeleton--text"></div>
<div class="skeleton skeleton--title"></div>
<div class="skeleton skeleton--avatar"></div>
<div class="skeleton skeleton--card"></div>Static gray on prefers-reduced-motion.
Badge
<span class="badge badge--success">Active</span>
<span class="badge badge--error"><span class="badge__dot"></span>Offline</span>Variants: --accent, --success, --error, --warning, --info, --glass.
Sizes: --sm, --lg.
Spinner
<span class="spinner"></span>
<span class="spinner spinner--dots"></span>
<span class="spinner spinner--pulse"></span>Sizes: --sm, --lg.
Tooltip
<button data-tooltip="Save document">Save</button>
<button data-tooltip="Delete" class="tooltip--below">Delete</button>
<button data-tooltip="Help" class="tooltip--right">?</button>
<button data-tooltip="Light glass" class="tooltip--light">Light</button>Zero JS. Positions: default (above), .tooltip--below, .tooltip--left, .tooltip--right.
| CSS property | Default |
|---|---|
| --tooltip-bg | rgba(20,24,36,0.88) |
| --tooltip-blur | 12px |
| --tooltip-delay | 0.15s |
Tree-shaking
Import only what you need:
<link rel="stylesheet" href="https://unpkg.com/arc-ui/src/base.css">
<link rel="stylesheet" href="https://unpkg.com/arc-ui/src/button.css">
<link rel="stylesheet" href="https://unpkg.com/arc-ui/src/modal.css">Browser support
| Feature | Chrome | Firefox | Safari | Edge |
|---------|--------|---------|--------|------|
| backdrop-filter | 76+ | 103+ | 9+ | 79+ |
| <dialog> | 37+ | 98+ | 15.4+ | 79+ |
| <details>/<summary> | 12+ | 49+ | 6+ | 79+ |
| Height animation (interpolate-size) | 129+ | — | — | 129+ |
| CSS custom properties | 49+ | 31+ | 9.1+ | 15+ |
Graceful degradation: backdrop-filter unsupported = opaque background. Height animation unsupported = instant expand/collapse.
Accessibility
- Modal: focus trapped (Tab/Shift-Tab), Escape closes, native
<dialog>semantics - Dropdown: native
<details>/<summary>(Escape + click-outside free),↑/↓/Tab keyboard nav,role="menu" - Drawer: focus trap, Escape closes, body scroll locked,
aria-hidden - Tabs: full ARIA (
role="tablist",aria-selected), ←/→/Home/End navigation - Accordion: native
<details>/<summary>— full browser keyboard + screen reader support - Switch: native
<input type="checkbox">— full keyboard, screen reader, and form support - Tooltip:
:focus-visibletriggered (keyboard accessible), not just:hover prefers-reduced-motion: all animations instant, skeleton shimmer static
License
MIT © Arc contributors
