shane.logsdon.io β writing and projects on agentic workflows, web standards, and payments engineering.
A minimal, flat design system for developers. Inspired by Flowglad and Zed, built with native web components and zero dependencies.
- Minimal & Flat - No heavy shadows, no unnecessary transforms
- Editorial Confidence - Generous whitespace, deliberate typography
- Developer-First - Clean code blocks, precise interactions
- Native Web Components - No framework dependencies
- TypeScript - Full type safety and excellent DX
- Theme Support - Light and dark modes with unified accent colors
- Zero Runtime - Pure web standards, no runtime overhead
npm installStart the development server:
npm run devBuild for production:
npm run buildBuild the library then open the local playground (it loads dist/index.js):
npm run build
open index.htmlNeed ready-made layouts? See patterns.md for composition examples built from these primitives.
Interactive buttons with three visual variants and sizes. Use primary for main actions (submit, save), secondary for supporting actions (cancel, back), and ghost for low-emphasis actions (help, dismiss). Ideal for forms, modals, toolbars, and CTAs.
<div style="display: flex; gap: 1rem; flex-wrap: wrap">
<dev-button theme="light" variant="primary">Primary</dev-button>
<dev-button theme="light" variant="secondary">Secondary</dev-button>
<dev-button theme="light" variant="ghost">Ghost</dev-button>
<dev-button theme="light" variant="primary" size="sm">Small</dev-button>
<dev-button theme="light" variant="primary" size="lg">Large</dev-button>
</div>Attributes:
theme-light | dark(default:light) - Token palette applied inside the shadow DOM.variant-primary | secondary | ghost(default:primary) - Visual treatment for fills/borders.size-sm | md | lg(default:md) - Padding and font scale.disabled-boolean(default:false) - Prevents interaction and dims the surface.loading-boolean(default:false) - Shows spinner and disables button during async operations.
Slots:
default- Button label or inline content.
Accessible checkbox for binary yes/no selections. Perfect for settings, preferences, terms acceptance, and multi-select lists. Supports keyboard navigation and screen readers. Use for independent options where multiple selections are allowed.
<div style="display: grid; gap: 0.75rem">
<dev-checkbox theme="light" checked="">Remember me</dev-checkbox>
<dev-checkbox theme="light">Email updates</dev-checkbox>
</div>Attributes:
theme-light | dark(default:light) - Token palette applied inside the shadow DOM.checked-boolean(default:false) - Controls the checked state.disabled-boolean(default:false) - Prevents interaction and lowers opacity.
Slots:
default- Label text.
Events:
dev-checkbox:change-CustomEvent<{ checked: boolean }>- Emitted when the checked state changes.
Versatile text input with built-in label, description, and error states. Supports single-line text or multiline textarea mode. Use for forms, search, user profile fields, and data entry. Includes validation states (success/error) and required field indicators.
<div style="display: grid; gap: 1rem">
<dev-input
theme="light"
label="Email"
required=""
description="We'll never share your email."
placeholder="you@example.com">
</dev-input>
<dev-input theme="light" label="Notes" multiline="" rows="4" help-text="Keep it concise."></dev-input>
<dev-input theme="light" label="Username" required="" error="Username is already taken"></dev-input>
</div>Attributes:
theme-light | dark(default:light) - Token palette applied inside the shadow DOM.label-string(default:"") - Top label text.description-string(default:"") - Helper text shown below the control.help-text-string(default:"") - Legacy helper text (falls back when description is absent).error-string(default:"") - Error message shown below the control.required-boolean(default:false) - Shows required indicator and sets required on the control.name-string(default:"") - Name attribute passed to the underlying control.type-string(default:text) - Input type (text, email, password, etc.). Ignored when multiline.state-default | success | error(default:default) - Tints borders and help text.multiline-boolean(default:false) - Render a textarea instead of input.rows-number(default:3) - Textarea rows when multiline.placeholder-string(default:"") - Placeholder text for the control.value-string(default:"") - Current value reflected on the control.disabled-boolean(default:false) - Prevents interaction and dims the control/label.
Slots:
default- Not used; component manages internal input.
Events:
dev-input:input-CustomEvent<{ value: string }>- Emitted on every keystroke as the user types.dev-input:change-CustomEvent<{ value: string; name: string }>- Emitted when the input loses focus after the value has changed.
Numeric input with +/- buttons for precise value control. Ideal for quantities, settings with bounded ranges (threads, retry counts), or any numeric value where users need fine-grained control. Prevents invalid input and respects min/max boundaries.
<dev-number-stepper theme="light" label="Concurrency" min="1" max="32" step="1" value="6"></dev-number-stepper>Attributes:
theme-light | dark(default:light) - Token palette applied inside the shadow DOM.value-number(default:0) - Current numeric value.min-number(default:0) - Lower bound; disables decrement below it.max-number(default:100) - Upper bound for value.step-number(default:1) - Increment/decrement step.
Slots:
default- Label text above the control.
Events:
dev-number-stepper:change-CustomEvent<{ value: number }>- Emitted when the value changes via increment/decrement buttons.
Mutually exclusive single-choice selector. Use standard radio style for forms/surveys, or segmented variant (pill-shaped buttons) for filter controls and view switchers. Perfect for pricing tiers, time intervals, and any scenario requiring exactly one selection.
<div style="display: grid; gap: 1rem">
<dev-radio-group theme="light" value="pro">
<option value="free">Free</option>
<option value="pro">Pro</option>
<option value="enterprise">Enterprise</option>
</dev-radio-group>
<dev-radio-group theme="light" variant="segmented" value="weekly">
<option value="daily">Daily</option>
<option value="weekly">Weekly</option>
<option value="monthly">Monthly</option>
</dev-radio-group>
</div>Attributes:
theme-light | dark(default:light) - Token palette applied inside the shadow DOM.value-string(default:"") - Selected option value.variant-radio | segmented(default:radio) - Visual style for options.orientation-horizontal | vertical(default:horizontal) - Layout direction.
Slots:
<option>- Option elements supply label/value and optional data-hint.
Events:
dev-radio-group:change-CustomEvent<{ value: string }>- Emitted when the selected option changes.
Visual range input for selecting numeric values by dragging. Best for approximate values, settings with visual feedback (volume, brightness, retention periods), or when the relative position matters more than exact numbers. More intuitive than steppers for large ranges.
<dev-slider theme="light" label="Retention days" min="7" max="90" value="30"></dev-slider>Attributes:
theme-light | dark(default:light) - Token palette applied inside the shadow DOM.value-number(default:50) - Current value within the range.min-number(default:0) - Lower bound for the slider.max-number(default:100) - Upper bound for the slider.step-number(default:1) - Increment granularity.
Slots:
default- Label displayed above the track.
Events:
dev-slider:change-CustomEvent<{ value: number }>- Emitted when the slider value changes.
On/off switch for instant state changes. Use instead of checkbox when the action takes immediate effect (like enabling a feature or switching modes). Common in settings panels, feature flags, and environment switchers. Provides clear visual feedback for binary states.
<div style="display: grid; gap: 0.75rem">
<dev-toggle theme="light" checked="">Live mode</dev-toggle>
<dev-toggle theme="light">Preview mode</dev-toggle>
</div>Attributes:
theme-light | dark(default:light) - Token palette applied inside the shadow DOM.checked-boolean(default:false) - On/off state.disabled-boolean(default:false) - Prevents interaction.
Slots:
default- Label text.
Events:
dev-toggle:change-CustomEvent<{ checked: boolean }>- Emitted when the toggle state changes.
Visually connects related buttons with shared borders. Perfect for alignment controls, view switchers (list/grid/gallery), or formatting toolbars. Groups logically related actions to reduce visual clutter. Supports horizontal or vertical orientation.
<div style="display: grid; gap: 1rem">
<dev-button-group theme="light">
<dev-button theme="light" variant="secondary">Left</dev-button>
<dev-button theme="light" variant="secondary">Center</dev-button>
<dev-button theme="light" variant="secondary">Right</dev-button>
</dev-button-group>
<dev-button-group theme="light" orientation="vertical">
<dev-button theme="light" variant="secondary">Top</dev-button>
<dev-button theme="light" variant="secondary">Middle</dev-button>
<dev-button theme="light" variant="secondary">Bottom</dev-button>
</dev-button-group>
</div>Attributes:
theme-light | dark(default:light) - Token palette applied inside the shadow DOM.orientation-horizontal | vertical(default:horizontal) - Layout direction for the group.
Slots:
default- Button children to group together.
Combines text inputs with prefix/suffix labels or icons. Essential for URLs (https://), currency ($ USD), units (%, GB, ms), or decorative icons (search magnifying glass). Creates clear context for input values and reduces user confusion.
<div style="display: grid; gap: 1rem">
<dev-input-group theme="light">
<span slot="prefix">https://</span>
<input type="text" placeholder="example.com" style="outline: none; padding: 0.75rem" />
</dev-input-group>
<dev-input-group theme="light">
<input type="number" placeholder="0.00" style="outline: none; padding: 0.75rem" />
<span slot="suffix">USD</span>
</dev-input-group>
</div>Attributes:
theme-light | dark(default:light) - Token palette applied inside the shadow DOM.disabled-boolean(default:false) - Disables the entire group.
Slots:
prefix- Content shown before the input.default- Input element in the middle.suffix- Content shown after the input.
Toggle buttons supporting single or multiple selections. Use single mode for alignment/view controls (mutually exclusive). Use multiple mode for text formatting (bold + italic + underline) or filter tags. More visual than radio groups or checkboxes.
<div style="display: grid; gap: 1rem">
<dev-toggle-group theme="light" type="single" value="center">
<span value="left">Left</span>
<span value="center">Center</span>
<span value="right">Right</span>
</dev-toggle-group>
<dev-toggle-group theme="light" type="multiple" value="bold,italic">
<span value="bold">B</span>
<span value="italic">I</span>
<span value="underline">U</span>
</dev-toggle-group>
</div>Attributes:
theme-light | dark(default:light) - Token palette applied inside the shadow DOM.type-single | multiple(default:single) - Selection mode for the group.orientation-horizontal | vertical(default:horizontal) - Layout direction.value-string | string[](default:"") - Selected value(s). Comma-separated for multiple.disabled-boolean(default:false) - Disables all buttons in the group.
Slots:
default- Elements with value attributes to toggle.
Events:
dev-toggle-group:change-CustomEvent<{ value: string | string[] }>- Emitted when the selection changes.
Dropdown menu for choosing from predefined options. Use for small-to-medium lists (under 20 items) where users pick one or multiple values. Supports keyboard navigation (arrows, Enter, Esc). For searchable lists, use Combobox instead. Common in filters, settings, and forms.
<div style="display: grid; gap: 1rem; max-width: 320px">
<dev-select theme="light" placeholder="Choose a framework" value="react">
<option value="react">React</option>
<option value="vue">Vue</option>
<option value="svelte">Svelte</option>
<option value="solid">Solid</option>
</dev-select>
<dev-select theme="light" placeholder="Choose languages" multiple="" value="ts,js">
<option value="ts">TypeScript</option>
<option value="js">JavaScript</option>
<option value="py">Python</option>
<option value="go">Go</option>
</dev-select>
</div>Attributes:
theme-light | dark(default:light) - Token palette applied inside the shadow DOM.placeholder-string(default:Select option) - Text shown when no value is selected.value-string | string[](default:"") - Selected value(s). Comma-separated for multiple.disabled-boolean(default:false) - Prevents interaction with the select.multiple-boolean(default:false) - Enables multi-select mode with checkmarks.open-boolean(default:false) - Controls dropdown visibility.
Slots:
<option>- Option elements provide label and value for selection.
Events:
dev-select:change-CustomEvent<{ value: string | string[] }>- Emitted when the selection changes.
Searchable dropdown with live filtering. Essential for long lists (countries, timezones, libraries) where typing is faster than scrolling. Combines text input with select functionality. Use when users know what they're looking for or lists exceed 20+ items.
<div style="max-width: 320px">
<dev-combobox theme="light" placeholder="Search frameworksβ¦" value="react">
<option value="react">React</option>
<option value="vue">Vue</option>
<option value="angular">Angular</option>
<option value="svelte">Svelte</option>
<option value="solid">Solid</option>
<option value="preact">Preact</option>
<option value="lit">Lit</option>
</dev-combobox>
</div>Attributes:
theme-light | dark(default:light) - Token palette applied inside the shadow DOM.placeholder-string(default:Search…) - Placeholder text for the search input.value-string(default:"") - Currently selected value.disabled-boolean(default:false) - Prevents interaction with the combobox.open-boolean(default:false) - Controls dropdown visibility.
Slots:
<option>- Option elements provide searchable items with label and value.
Events:
dev-combobox:change-CustomEvent<{ value: string; label: string }>- Emitted when an option is selected.
Visual calendar for selecting dates. Supports single dates (appointments, deadlines), multiple dates (recurring events), or date ranges (booking systems, reports). Better UX than manual text entry. Prevents invalid dates and formats consistently as ISO strings.
<div style="display: grid; gap: 1rem; max-width: 340px">
<dev-date-picker
id="datePickerRange"
theme="light"
label="Availability"
mode="range"
value='["2024-08-05","2024-08-09"]'>
</dev-date-picker>
<dev-date-picker id="datePickerSingle" theme="light" placeholder="Pick a launch date"></dev-date-picker>
</div>
<script>
const rangePicker = document.getElementById("datePickerRange");
rangePicker?.addEventListener("date-change", (e) => {
console.log("Date changed", e.detail);
});
</script>Attributes:
theme-light | dark(default:light) - Token palette applied inside the shadow DOM.mode-single | multiple | range(default:single) - Selection behavior.value-string | JSON(default:"") - ISO string for single, JSON array for multiple/range.placeholder-string(default:Select date) - Text when no value is selected.label-string(default:"") - Optional top label.disabled-boolean(default:false) - Prevents toggling the calendar and input focus.
Slots:
default- Not used; controlled via attributes.
Events:
dev-date-picker:change-CustomEvent<{ mode: string; selected: string | string[] }>- Emitted when date selection changes.
Specialized input for one-time passwords and verification codes. Auto-advances between character slots for seamless entry. Perfect for 2FA flows, SMS verification, or security codes. Handles paste events intelligently and provides clear visual feedback per digit.
<div style="display: grid; gap: 1rem">
<dev-input-otp theme="light" length="6" type="number"></dev-input-otp>
<dev-input-otp theme="light" length="4" type="number"></dev-input-otp>
</div>Attributes:
theme-light | dark(default:light) - Token palette applied inside the shadow DOM.length-number(default:6) - Number of character slots for the OTP.type-text | number(default:number) - Input type (numeric or alphanumeric).value-string(default:"") - Current OTP value.disabled-boolean(default:false) - Prevents input interaction.
Slots:
default- No slots; controlled by attributes.
Events:
dev-input-otp:change-CustomEvent<{ value: string }>- Emitted when the OTP value changes.dev-input-otp:complete-CustomEvent<{ value: string }>- Emitted when all OTP slots are filled.
Container for managing form state, validation, and submissions. Automatically collects values from named inputs and dispatches form-submit events with structured data. Handles reset/submit buttons. Use for login, signup, settings, or any multi-field data entry.
<dev-form id="demoForm" theme="light">
<div style="display: grid; gap: 1rem; max-width: 400px">
<dev-input theme="light" label="Email" required="" name="email" placeholder="you@example.com"></dev-input>
<dev-input theme="light" label="Password" required="" name="password" type="password" placeholder="β’β’β’β’β’β’β’β’">
</dev-input>
<div style="display: flex; gap: 0.75rem">
<dev-button theme="light" type="submit">Submit</dev-button>
<dev-button theme="light" variant="secondary" type="reset">Reset</dev-button>
</div>
</div>
</dev-form>
<script>
const demoForm = document.getElementById("demoForm");
demoForm?.addEventListener("form-submit", (e) => {
console.log("Form submitted:", e.detail.data);
alert("Form submitted! Check console for data.");
});
</script>Events:
dev-form:submit-CustomEvent<{ data: Record<string, unknown>; formData: FormData }>- Emitted when the form is submitted.dev-form:reset-CustomEvent<{}>- Emitted when the form is reset.
Shows hierarchical page location for easy navigation. Essential for deep page structures (docs, admin panels, e-commerce categories). Helps users understand where they are and provides quick back-navigation. Uses aria-current for accessibility.
<dev-breadcrumbs theme="light">
<a href="/">Home</a>
<a href="/billing">Billing</a>
<a aria-current="page">Invoices</a>
</dev-breadcrumbs>Attributes:
theme-light | dark(default:light) - Token palette applied inside the shadow DOM.items-JSON(default:n/a) - Optional JSON array of {label, href} if not using links.
Slots:
<a>- Anchor elements for each crumb (preferred).
Navigate through multi-page datasets. Use for tables, search results, or any list exceeding 10-50 items. Shows current page, total pages, and prev/next controls. Dispatches page-change events for easy integration with data fetching.
<dev-pagination theme="light" page="3" total-pages="9"></dev-pagination>Attributes:
theme-light | dark(default:light) - Token palette applied inside the shadow DOM.page-number(default:1) - Current page index (1-based).total-pages-number(default:1) - Total number of pages.
Slots:
default- No slots; buttons are generated.
Events:
dev-pagination:change-CustomEvent<{ page: number; previousPage: number }>- Emitted when the page changes.
Visual progress tracker for multi-step processes. Perfect for onboarding flows, checkout funnels, setup wizards, or form wizards. Shows completed/current/upcoming steps clearly. Helps users understand progress and reduces abandonment.
<dev-steps
theme="light"
current="1"
steps='[{"label":"Create project"},{"label":"Connect billing"},{"label":"Deploy"}]'>
</dev-steps>Attributes:
theme-light | dark(default:light) - Token palette applied inside the shadow DOM.steps-JSON(default:[]) - Array of {label, description, state} objects.current-number(default:0) - Index for current step.orientation-horizontal | vertical(default:horizontal) - Layout direction.
Slots:
default- Optional child nodes with data-label/description.
Switch between related content panels without page reload. Use for dashboards (Overview/Analytics/Settings), product pages (Details/Reviews/Specs), or documentation (Guide/API/Examples). Keeps context while reducing scroll and clutter.
<dev-tabs theme="light">
<div data-label="Overview">
<p>Keep pricing, roadmap, and API docs close together.</p>
</div>
<div data-label="API">
<dev-code-block theme="light" language="typescript" filename="client.ts"
>import { Client } from '@modern-dev/sdk'; const client = new Client({ apiKey: process.env.API_KEY }); await
client.deploy();</dev-code-block
>
</div>
<div data-label="Changelog">
<p>Ship weekly updates with clear notes for your team.</p>
</div>
</dev-tabs>Attributes:
theme-light | dark(default:light) - Token palette applied inside the shadow DOM.selected-index-number(default:0) - Active tab index.
Slots:
<div data-label>- Panels with data-label attribute become tabs.
Events:
dev-tabs:change-CustomEvent<{ selectedIndex: number; previousIndex: number }>- Emitted when the active tab changes.
Page or section header with title and action buttons. Common in admin interfaces, detail pages, and modals. Provides consistent header pattern with clear hierarchy: title on left, actions on right. Supports optional eyebrow text for context.
<dev-toolbar theme="light">
<span slot="title">Billing</span>
<dev-cluster slot="actions" gap="0.5rem">
<dev-button theme="light" variant="secondary" size="sm">Cancel</dev-button>
<dev-button theme="light" variant="secondary" size="sm">Save</dev-button>
</dev-cluster>
</dev-toolbar>Attributes:
theme-light | dark(default:light) - Token palette applied inside the shadow DOM.
Slots:
title- Primary title content.eyebrow- Optional label above the title.actions- Right-aligned action area.
Desktop-style horizontal menu bar with nested dropdowns and keyboard shortcuts. Use for app navigation (File/Edit/View), developer tools, or content editors. Provides familiar desktop UX with full keyboard support (arrows, Enter, Esc).
<div>
<dev-menubar id="appMenubar" theme="light"></dev-menubar>
</div>
<script>
window.addEventListener("DOMContentLoaded", () => {
const menubar = document.getElementById("appMenubar");
if (menubar && menubar.setItems) {
menubar.setItems([
{
label: "File",
items: [
{ label: "New File", shortcut: "Cmd+N" },
{ label: "Open File", shortcut: "Cmd+O" },
{ separator: true },
{ label: "Save", shortcut: "Cmd+S" },
{ label: "Save Asβ¦", shortcut: "Cmd+Shift+S" },
],
},
{
label: "Edit",
items: [
{ label: "Undo", shortcut: "Cmd+Z" },
{ label: "Redo", shortcut: "Cmd+Shift+Z" },
{ separator: true },
{ label: "Cut", shortcut: "Cmd+X" },
{ label: "Copy", shortcut: "Cmd+C" },
{ label: "Paste", shortcut: "Cmd+V" },
],
},
{
label: "View",
items: [
{ label: "Zoom In", shortcut: "Cmd++" },
{ label: "Zoom Out", shortcut: "Cmd+-" },
{ label: "Reset Zoom", shortcut: "Cmd+0" },
],
},
]);
}
});
</script>Events:
dev-menubar:item-select-CustomEvent<{ item: HTMLElement }>- Emitted when a menu item is selected.
Multi-level navigation with nested dropdown menus. Perfect for marketing sites, e-commerce (category hierarchies), or knowledge bases. Supports unlimited nesting depth. More web-native than Menubar, less desktop-focused.
<div>
<dev-navigation-menu id="navMenu" theme="light"></dev-navigation-menu>
</div>
<script>
window.addEventListener("DOMContentLoaded", () => {
const navMenu = document.getElementById("navMenu");
if (navMenu && navMenu.setItems) {
navMenu.setItems([
{ label: "Home", href: "#home" },
{
label: "Products",
children: [
{ label: "All Products", href: "#products" },
{ label: "New Arrivals", href: "#new" },
{
label: "Categories",
children: [
{ label: "Electronics", href: "#electronics" },
{ label: "Clothing", href: "#clothing" },
{ label: "Books", href: "#books" },
],
},
],
},
{ label: "About", href: "#about" },
{ label: "Contact", href: "#contact" },
]);
}
});
</script>Events:
dev-navigation-menu:navigate-CustomEvent<{ item: HTMLElement }>- Emitted when a navigation item is clicked.
Inline status messages for contextual feedback. Use info for tips, success for confirmations, warning for cautions, danger for errors. Place near related content (forms, sections). Supports dismissal for non-critical messages. More subtle than modals.
<div style="display: grid; gap: 1rem">
<dev-alert theme="light" variant="info" heading="Heads up">We rotate keys every 24 hours.</dev-alert>
<dev-alert theme="light" variant="success" heading="Shipped" closable="">New billing API is live.</dev-alert>
<dev-alert theme="light" variant="danger" heading="Action required" closable="">Update your webhook URL.</dev-alert>
</div>Attributes:
theme-light | dark(default:light) - Token palette applied inside the shadow DOM.variant-info | success | warning | danger | neutral(default:info) - Color system for the alert.closable-boolean(default:false) - Shows a dismiss button when set.heading-string(default:"") - Optional heading text.
Slots:
default- Body copy of the alert.
Events:
dev-alert:close-CustomEvent<{ reason: string }>- Emitted when the alert is dismissed.
Prominent announcement banner for page-level messaging. Use for feature announcements, breaking changes, or important notices that apply to the entire page/section. More prominent than Alert but less intrusive than Modal. Great for changelogs.
<dev-callout theme="light" tone="info" title="Preview tier launched"
>Use the new preview tier for test tenants without touching production billing.</dev-callout
>Attributes:
theme-light | dark(default:light) - Token palette applied inside the shadow DOM.tone-info | success | warning | danger | neutral(default:info) - Status palette for the surface.title-string(default:"") - Headline for the callout.
Slots:
default- Body content.
Centered overlay dialog requiring user action before dismissal. Use for confirmations (delete account), forms (create resource), or critical decisions. Blocks page interaction until closed. Includes focus trap for accessibility. Choose Sheet for less critical content.
<dev-modal theme="light" title="Create API key" open="" size="md">
<p>Generate a scoped key for CI usage and rotation.</p>
<dev-stack theme="light" gap="0.75rem">
<dev-input theme="light" label="Name" placeholder="CI deploy key"></dev-input>
<dev-input theme="light" label="Scope" placeholder="billing.write"></dev-input>
<div style="display: flex; gap: 0.75rem; justify-content: flex-end">
<dev-button theme="light" variant="secondary" size="sm">Cancel</dev-button>
<dev-button theme="light" size="sm">Create key</dev-button>
</div>
</dev-stack>
</dev-modal>Attributes:
theme-light | dark(default:light) - Token palette applied inside the shadow DOM.open-boolean(default:false) - Controls visibility of the dialog.size-sm | md | lg(default:md) - Max width preset for the dialog.closable-boolean(default:true) - Allows overlay/ESC close when true.title-string(default:"") - Optional heading for aria-labeling.
Slots:
default- Dialog body content and actions.
Events:
dev-modal:close-CustomEvent<{ reason: "escape" | "overlay" | "button" }>- Emitted when the modal is closed.
Linear progress indicator for determinate tasks. Shows completion percentage for uploads, downloads, builds, or multi-step processes. Use when you know total duration/steps. For indeterminate loading, use Spinner instead.
<div style="display: grid; gap: 1rem">
<dev-progress theme="light" label="Provisioning" value="64"></dev-progress>
<dev-progress theme="light" label="Integration tests" value="18" max="24" show-value="true"></dev-progress>
</div>Attributes:
theme-light | dark(default:light) - Token palette applied inside the shadow DOM.value-number(default:0) - Current progress value.max-number(default:100) - Upper bound for the progress bar.label-string(default:"") - Accessible label and visible text.show-value-boolean(default:true) - Toggle display of numeric percentage.
Slots:
default- No slots; value is driven by attributes.
Placeholder blocks mimicking content structure during load. Better UX than blank space or spinners for content-heavy pages. Shows approximate layout before data arrives. Use for cards, lists, profiles, or text blocks.
<div style="display: grid; gap: 0.75rem; max-width: 420px">
<dev-skeleton theme="light" width="100%" height="16px"></dev-skeleton>
<dev-skeleton theme="light" width="70%" height="16px"></dev-skeleton>
<dev-skeleton theme="light" width="90%" height="48px"></dev-skeleton>
</div>Attributes:
theme-light | dark(default:light) - Token palette applied inside the shadow DOM.width-string(default:100%) - CSS width of the placeholder.height-string(default:1.5rem) - CSS height of the placeholder.rounded-boolean(default:true) - Toggle rounded corners.
Slots:
default- Decorative; no slots used.
Indeterminate loading indicator for unknown durations. Use inline with text (saving changesβ¦) or centered in loading states. Smaller footprint than Progress for quick operations. Signals activity without specific progress.
<div style="display: flex; align-items: center; gap: 0.75rem">
<dev-spinner theme="light"></dev-spinner>
<span style="color: rgb(111, 102, 88)">Fetching dataβ¦</span>
</div>Attributes:
theme-light | dark(default:light) - Token palette applied inside the shadow DOM.size-string(default:1.5rem) - Diameter of the spinner.
Slots:
default- Decorative; no slots used.
Non-blocking notifications that auto-dismiss. Perfect for confirmations (saved, copied, sent), errors (network timeout), or background task updates. Stacks multiple toasts and queues them intelligently. Doesn't interrupt workflow like modals.
<dev-toast-stack id="toastStack" theme="light"></dev-toast-stack>
<div style="display: flex; gap: 0.75rem; flex-wrap: wrap">
<dev-button id="toastSuccess" theme="light" size="sm">Success</dev-button>
<dev-button id="toastInfo" theme="light" size="sm" variant="secondary">Info</dev-button>
</div>
<script>
const toastStack = document.getElementById("toastStack");
const successBtn = document.getElementById("toastSuccess");
const infoBtn = document.getElementById("toastInfo");
successBtn?.addEventListener("click", () => {
toastStack?.pushToast?.({ title: "Saved", message: "Settings updated", variant: "success" });
});
infoBtn?.addEventListener("click", () => {
toastStack?.pushToast?.({ title: "Heads up", message: "Deploy queued", variant: "info" });
});
</script>Attributes:
theme-light | dark(default:light) - Token palette applied inside the shadow DOM.
Slots:
default- Mounted stack; trigger via queue-toast events.
Contextual hints shown on hover or focus. Use for icon buttons (what does this do?), truncated text, or supplementary info. Keep text brief (under 10 words). Accessible via keyboard. Don't hide critical info in tooltips.
<dev-tooltip theme="light" text="Copy to clipboard" placement="right">
<dev-button slot="trigger" theme="light" variant="secondary" size="sm">Hover me</dev-button>
</dev-tooltip>Attributes:
theme-light | dark(default:light) - Token palette applied inside the shadow DOM.text-string(default:"") - Message content (or use default slot).placement-top | bottom | left | right(default:top) - Tooltip position relative to trigger.open-boolean(default:false) - Force open state for testing.
Slots:
trigger- Element that receives hover/focus handlers.default- Optional custom tooltip body instead of text attr.
Rich content overlay anchored to trigger element. Use for menus, option pickers, or content that needs more space than tooltips. Click-triggered (vs tooltip's hover). Great for filter panels, color pickers, or inline forms.
<div style="display: flex; gap: 1rem; flex-wrap: wrap">
<dev-button id="popoverTrigger" theme="light" variant="secondary" size="sm">Open Popover</dev-button>
<dev-popover id="myPopover" theme="light" placement="bottom">
<p style="margin: 0px">This is a popover with additional content and actions.</p>
</dev-popover>
</div>
<script>
window.addEventListener("DOMContentLoaded", () => {
const popover = document.getElementById("myPopover");
const trigger = document.getElementById("popoverTrigger");
if (popover && trigger && popover.setTrigger && popover.toggle) {
popover.setTrigger(trigger);
trigger.addEventListener("click", () => popover.toggle());
}
});
</script>Attributes:
theme-light | dark(default:light) - Token palette applied inside the shadow DOM.open-boolean(default:false) - Controls visibility of the popover.placement-top | top-start | top-end | bottom | bottom-start | bottom-end | left | left-start | left-end | right | right-start | right-end(default:bottom) - Position relative to trigger.offset-number(default:8) - Distance from trigger in pixels.
Slots:
default- Popover content.
Preview card triggered by hover (with delay). Perfect for user profiles (@mentions), link previews, or item details in lists. Provides rich context without navigation. More patient than tooltips (waits before showing).
<div style="display: flex; gap: 0.5rem; align-items: center">
<span id="hoverCardTrigger" style="text-decoration: underline; cursor: pointer">@username</span>
<dev-hover-card id="userCard" theme="light" placement="right">
<div slot="title" style="font-weight: 600; margin-bottom: 0.5rem">User Profile</div>
<p style="margin: 0px; font-size: 0.9rem; color: rgb(111, 102, 88)">
Full-stack developer with 5 years of experience building web applications.
</p>
</dev-hover-card>
</div>
<script>
window.addEventListener("DOMContentLoaded", () => {
const hoverCard = document.getElementById("userCard");
const trigger = document.getElementById("hoverCardTrigger");
if (hoverCard && trigger && hoverCard.setTrigger) {
hoverCard.setTrigger(trigger);
}
});
</script>Attributes:
theme-light | dark(default:light) - Token palette applied inside the shadow DOM.open-boolean(default:false) - Controls visibility of the card.placement-top | bottom | left | right(default:top) - Position relative to trigger.open-delay-number(default:200) - Delay in ms before opening.close-delay-number(default:300) - Delay in ms before closing.
Slots:
trigger- Element that triggers the hover card.title- Card title content.description- Card description content.default- Additional card content.
Slide-in panel from screen edges (right/left/top/bottom). Use for detail views, filters, shopping carts, or settings. Less blocking than Modal, more spacious than Popover. Mobile-friendly (especially bottom sheets). Keeps main content visible.
<div style="display: flex; gap: 0.75rem; flex-wrap: wrap">
<dev-button id="sheetBtn1" theme="light" variant="secondary" size="sm">Open Right Sheet</dev-button>
<dev-button id="sheetBtn2" theme="light" variant="secondary" size="sm">Open Bottom Sheet</dev-button>
<dev-sheet id="sheet1" theme="light" side="right" title="Sheet Title">
<p style="margin: 0px 0px 1rem 0px">This is a sheet that slides in from the right side.</p>
<p style="margin: 0px">Use sheets for secondary content, forms, or detail views.</p>
</dev-sheet>
<dev-sheet id="sheet2" theme="light" side="bottom" title="Bottom Sheet">
<p style="margin: 0px">This sheet slides in from the bottom.</p>
</dev-sheet>
</div>
<script>
window.addEventListener("DOMContentLoaded", () => {
const sheet1 = document.getElementById("sheet1");
const sheet2 = document.getElementById("sheet2");
const btn1 = document.getElementById("sheetBtn1");
const btn2 = document.getElementById("sheetBtn2");
if (btn1 && sheet1) {
btn1.addEventListener("click", () => {
sheet1.open = true;
});
}
if (btn2 && sheet2) {
btn2.addEventListener("click", () => {
sheet2.open = true;
});
}
});
</script>Attributes:
theme-light | dark(default:light) - Token palette applied inside the shadow DOM.open-boolean(default:false) - Controls visibility of the sheet.side-left | right | top | bottom(default:right) - Edge from which the sheet slides in.title-string(default:"") - Optional title for the sheet header.
Slots:
default- Sheet content area.
Events:
dev-sheet:close-CustomEvent<{ reason: "escape" | "overlay" | "button" }>- Emitted when the sheet is closed.
Collapsible content sections to save vertical space. Perfect for FAQs, documentation, settings groups, or anywhere dense information needs progressive disclosure. Supports single or multiple open panels. Improves scanability and reduces overwhelming UI.
<dev-accordion theme="light" multiple="">
<div data-label="Setup">
<p>Install the CLI and log in with your API key.</p>
</div>
<div data-label="Billing">
<p>Track invoices, payouts, and tax settings in one place.</p>
</div>
<div data-label="Security">
<p>Rotate signing keys every 24 hours with one click.</p>
</div>
</dev-accordion>Attributes:
theme-light | dark(default:light) - Token palette applied inside the shadow DOM.multiple-boolean(default:false) - Allow multiple panels open at once.open-indexes-string(default:0) - Comma-delimited open indices.
Slots:
<div data-label>- Section content; data-label becomes trigger text.
Events:
dev-accordion:toggle-CustomEvent<{ index: number; open: boolean }>- Emitted when an accordion section is toggled.
Visual user representation showing profile image or initials fallback. Use in headers, comments, team lists, or anywhere user identity matters. Automatically generates colored backgrounds based on name. Supports multiple sizes for different contexts.
<div style="display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap">
<dev-avatar theme="light" name="Ada Lovelace"></dev-avatar>
<dev-avatar theme="light" name="Grace Hopper" src="https://i.pravatar.cc/64?img=1"></dev-avatar>
<dev-avatar theme="light" name="Linus Torvalds" size="lg"></dev-avatar>
</div>Attributes:
theme-light | dark(default:light) - Token palette applied inside the shadow DOM.src-string(default:"") - Image source for the avatar.name-string(default:"") - Used for initials and aria-label.size-sm | md | lg(default:md) - Avatar dimensions.
Slots:
default- No slots; set src/name attributes.
Compact display of multiple users with overflow handling. Shows team members, collaborators, or participants without taking excessive space. Displays count when exceeding max ("+2 more"). Perfect for project cards, document collaborators, or event attendees.
<dev-avatar-group theme="light" max="3">
<dev-avatar theme="light" name="Alex Kim"></dev-avatar>
<dev-avatar theme="light" name="Morgan Lee"></dev-avatar>
<dev-avatar theme="light" name="Jordan Smith"></dev-avatar>
<dev-avatar theme="light" name="Taylor Shaw"></dev-avatar>
</dev-avatar-group>Attributes:
theme-light | dark(default:light) - Token palette applied inside the shadow DOM.max-number(default:4) - Maximum avatars before overflow counter.
Slots:
<dev-avatar>- Avatar children to render in the stack.
Small status indicator for metadata and states. Use for labels (Beta, New, Pro), statuses (Active, Pending, Failed), or categories. Color-coded variants provide instant visual meaning. Compact enough for inline use within text or table cells.
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap">
<dev-badge theme="light" variant="neutral">Beta</dev-badge>
<dev-badge theme="light" variant="info">Preview</dev-badge>
<dev-badge theme="light" variant="success">Deployed</dev-badge>
<dev-badge theme="light" variant="warning">Limited</dev-badge>
<dev-badge theme="light" variant="danger">Breaking</dev-badge>
</div>Attributes:
theme-light | dark(default:light) - Token palette applied inside the shadow DOM.variant-neutral | accent | info | success | warning | danger(default:neutral) - Color palette for the badge.
Slots:
default- Inline text/emoji/icon.
Flexible content container with optional header and footer. Universal building block for dashboards, product grids, blog posts, or any grouped content. Supports elevation levels for depth. Provides consistent padding and visual boundaries.
<dev-card theme="light">
<div slot="header">
<strong>Card Header</strong>
</div>
<div>
<p>Card content lives here. Keep it simple and readable.</p>
</div>
<div slot="footer">Footer actions</div>
</dev-card>Attributes:
theme-light | dark(default:light) - Token palette applied inside the shadow DOM.elevation-none | sm | md | lg(default:md) - Shadow preset for the surface.padding-spacing token(default:6) - Padding scale using spacing tokens.
Slots:
header- Top content row.default- Body content.footer- Footer actions or meta.
Syntax-highlighted code display with optional filename and line numbers. Essential for documentation, tutorials, API references, or blogs. Provides professional code presentation with language detection. Includes copy-to-clipboard functionality.
<dev-code-block theme="light" language="typescript" filename="example.ts"
>const greet = (name: string) => { return 'Hello, ' + name + '!'; };</dev-code-block
>Attributes:
theme-light | dark(default:light) - Token palette applied inside the shadow DOM.language-string(default:typescript) - Language hint for highlighting.filename-string(default:"") - Optional top label for the code.show-line-numbers-boolean(default:true) - Toggles line number gutter.
Slots:
default- Code content as text.
Friendly placeholder for empty lists, searches, or sections. Guides users to first action instead of showing blank space. Reduces confusion and abandonment. Include clear CTAs to help users get started. Better UX than "No results found".
<dev-empty-state
theme="light"
title="No monitors yet"
description="Create your first check to keep uptime in sight."
icon="β±">
<dev-button slot="actions" theme="light">Create monitor</dev-button>
<dev-button slot="actions" theme="light" variant="secondary">View docs</dev-button>
</dev-empty-state>Attributes:
theme-light | dark(default:light) - Token palette applied inside the shadow DOM.title-string(default:"") - Headline for the empty state.description-string(default:"") - Supporting text.icon-string(default:O) - Emoji or character to display.
Slots:
actions- Action buttons placed below text.
Simple data table with optional sorting. Use for small-to-medium datasets (under 100 rows) with basic display needs. Handles empty and loading states gracefully. For advanced features (filtering, pagination), use DataTable instead.
<dev-table
theme="light"
columns='[{"key":"name","label":"Name","sortable":true},{"key":"role","label":"Role"},{"key":"status","label":"Status","sortable":true}]'
rows='[{"name":"api.gateway","role":"Service","status":"Healthy"},{"name":"billing.worker","role":"Worker","status":"Degraded"},{"name":"docs","role":"Static","status":"Healthy"}]'>
</dev-table>Attributes:
theme-light | dark(default:light) - Token palette applied inside the shadow DOM.columns-JSON(default:[]) - Column definitions with label/sortable.rows-JSON(default:[]) - Row objects keyed by column keys.loading-boolean(default:false) - Shows loading placeholder row.empty-text-string(default:No data available) - Message when no rows exist.
Slots:
default- Not used; data-driven via attributes.empty-action- Optional action button shown in empty state.
Interactive labels for categories, filters, or selections. Use solid variant for static labels (keywords, topics). Enable filter mode for toggleable filters (search refinement). Add dismissible for removable selections (applied filters, email chips).
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap">
<dev-tag theme="light">Runtime</dev-tag>
<dev-tag theme="light" variant="outline" filter="" selected="">Serverless</dev-tag>
<dev-tag theme="light" dismissible="">Clear</dev-tag>
</div>Attributes:
theme-light | dark(default:light) - Token palette applied inside the shadow DOM.variant-solid | outline(default:solid) - Fill vs outline style.dismissible-boolean(default:false) - Show close button when set.filter-boolean(default:false) - Adds toggle behavior for filters.selected-boolean(default:false) - Selected state (when filter=true).
Slots:
default- Tag label.
Events:
dev-tag:dismiss-CustomEvent<{}>- Emitted when the tag is dismissed (dismissible=true).dev-tag:toggle-CustomEvent<{ selected: boolean }>- Emitted when the tag filter state is toggled (filter=true).
Forces content to maintain specific width:height ratio. Essential for responsive images, video embeds, or placeholder content that must preserve proportions. Prevents layout shift during load. Common ratios: 16:9 (video), 4:3 (photos), 1:1 (avatars).
<div style="display: grid; gap: 1rem; max-width: 600px">
<dev-aspect-ratio theme="light" ratio="16/9">
<div
style="
width: 100%;
height: 100%;
background: linear-gradient(135deg, #2e7d6e 0%, #276a5e 100%);
display: flex;
align-items: center;
justify-content: center;
color: white;
">
16:9 Aspect Ratio
</div>
</dev-aspect-ratio>
<dev-aspect-ratio theme="light" ratio="4/3">
<div
style="
width: 100%;
height: 100%;
background: linear-gradient(135deg, #6f6658 0%, #4a433a 100%);
display: flex;
align-items: center;
justify-content: center;
color: white;
">
4:3 Aspect Ratio
</div>
</dev-aspect-ratio>
</div>Attributes:
theme-light | dark(default:light) - Token palette applied inside the shadow DOM.ratio-string(default:16/9) - Aspect ratio as fraction (16/9) or decimal (1.77).
Slots:
default- Content to maintain aspect ratio for.
Horizontal content slider with navigation controls and indicators. Use for image galleries, testimonials, feature showcases, or product views. Supports auto-play, looping, and swipe gestures. Caution: can reduce accessibility and engagement if overused.
<div style="max-width: 600px">
<dev-carousel theme="light" show-indicators="" show-controls="" loop="">
<div
style="
width: 100%;
height: 300px;
background: linear-gradient(135deg, #2e7d6e 0%, #276a5e 100%);
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 2rem;
font-weight: 600;
">
Slide 1
</div>
<div
style="
width: 100%;
height: 300px;
background: linear-gradient(135deg, #6f6658 0%, #4a433a 100%);
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 2rem;
font-weight: 600;
">
Slide 2
</div>
<div
style="
width: 100%;
height: 300px;
background: linear-gradient(135deg, #084ccf 0%, #0639a1 100%);
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 2rem;
font-weight: 600;
">
Slide 3
</div>
</dev-carousel>
</div>Events:
dev-carousel:change-CustomEvent<{ currentIndex: number; previousIndex: number }>- Emitted when the active slide changes.
Standalone month grid for date selection without input field. Use when building custom date pickers or scheduling interfaces. Supports same modes as DatePicker (single/multiple/range). More flexible than DatePicker but requires custom UI integration.
<div style="max-width: 420px">
<dev-calendar id="demoCalendar" theme="light" mode="range" month="2024-08" selected='["2024-08-05","2024-08-08"]'>
</dev-calendar>
</div>
<script>
const demoCalendar = document.getElementById("demoCalendar");
demoCalendar?.addEventListener("date-select", (e) => {
console.log("Selected", e.detail);
});
</script>Attributes:
theme-light | dark(default:light) - Token palette applied inside the shadow DOM.mode-single | multiple | range(default:single) - Selection behavior.selected-string | JSON(default:"") - ISO date string (single) or JSON array for multiple/range.month-string(default:current month) - Month anchor as YYYY-MM.
Slots:
default- Not used; driven by attributes.
Events:
dev-calendar:select-CustomEvent<{ mode: string; selected: string | string[] }>- Emitted when date selection changes.
Simple SVG charts for basic data visualization. Use for dashboards, reports, or analytics views. Supports line (trends), bar (comparisons), and area (volumes) types. Lightweight alternative to heavy charting libraries for simple use cases.
<dev-chart
theme="light"
type="area"
data='[{"label":"Mon","value":12},{"label":"Tue","value":18},{"label":"Wed","value":9},{"label":"Thu","value":22},{"label":"Fri","value":16}]'>
</dev-chart>Attributes:
theme-light | dark(default:light) - Token palette applied inside the shadow DOM.type-line | bar | area(default:line) - Chart visualization style.data-JSON(default:[]) - Array of { label, value } or number list.
Slots:
default- Not used; data-driven via attributes.
Full-featured table with built-in filtering, sorting, and pagination. Use for large datasets (100+ rows), admin panels, or data-heavy applications. More powerful than Table component. Handles performance and UX concerns for big data automatically.
<dev-data-table
theme="light"
page-size="5"
columns='[{"key":"service","label":"Service","sortable":true},{"key":"region","label":"Region"},{"key":"status","label":"Status","sortable":true}]'
rows='[
{"service":"api.gateway","region":"us-east","status":"healthy"},
{"service":"billing","region":"us-east","status":"degraded"},
{"service":"auth","region":"eu-west","status":"healthy"},
{"service":"jobs","region":"us-west","status":"healthy"},
{"service":"metrics","region":"eu-west","status":"maintenance"},
{"service":"docs","region":"us-east","status":"healthy"}
]'>
</dev-data-table>Attributes:
theme-light | dark(default:light) - Token palette applied inside the shadow DOM.columns-JSON(default:[]) - Column definitions with label/sortable.rows-JSON(default:[]) - Row objects keyed by column keys.page-size-number(default:5) - Rows per page for pagination.loading-boolean(default:false) - Show loading placeholder row.empty-text-string(default:No results found) - Empty state copy.
Slots:
default- Not used; data-driven via attributes.empty-action- Optional action button shown in empty state.
Auto-responsive grid for card collections. Automatically adjusts columns based on viewport width. Use for blog previews, product grids, feature showcases, or any card-based content. Optional masonry mode for Pinterest-style layouts with varying heights.
<dev-card-layout theme="light">
<dev-card theme="light">
<p>Card A</p>
</dev-card>
<dev-card theme="light">
<p>Card B</p>
</dev-card>
<dev-card theme="light">
<p>Card C</p>
</dev-card>
</dev-card-layout>Attributes:
theme-light | dark(default:light) - Token palette applied inside the shadow DOM.gap-string(default:1rem) - Gap between tiles.columns-number(default:3) - Column hint when masonry=true.masonry-boolean(default:false) - Switch to CSS columns for masonry.
Slots:
default- Card children to distribute in the grid/columns.
Horizontal layout that wraps inline elements with consistent spacing. Perfect for button groups, tags, filters, or badges that should flow naturally. Automatically wraps to multiple rows when space is constrained. Simpler than Grid for linear content.
<dev-cluster theme="light" gap="0.75rem">
<dev-button theme="light" size="sm">Save</dev-button>
<dev-button theme="light" variant="secondary" size="sm">Cancel</dev-button>
<dev-button theme="light" variant="ghost" size="sm">Help</dev-button>
</dev-cluster>Attributes:
theme-light | dark(default:light) - Token palette applied inside the shadow DOM.gap-string(default:0.75rem) - Spacing between items.align-CSS align-items(default:center) - Cross-axis alignment.justify-CSS justify-content(default:flex-start) - Main-axis alignment.
Slots:
default- Inline children (buttons, badges, etc).
Responsive CSS grid that auto-fills columns based on minimum width. More flexible than CardLayout with customizable column behavior. Use for dashboard tiles, feature grids, or any equal-width content. Automatically adapts column count to available space.
<dev-grid theme="light" gap="1rem" min="180px">
<dev-card theme="light">
<p>Tile A</p>
</dev-card>
<dev-card theme="light">
<p>Tile B</p>
</dev-card>
<dev-card theme="light">
<p>Tile C</p>
</dev-card>
<dev-card theme="light">
<p>Tile D</p>
</dev-card>
</dev-grid>Attributes:
theme-light | dark(default:light) - Token palette applied inside the shadow DOM.gap-string(default:1rem) - Gap between grid items.columns-number(default:3) - Suggested column count for fallback layouts.min-string(default:240px) - Min column width for auto-fit behavior.
Slots:
default- Child cards/blocks to grid.
Full-width section with centered content container. Provides consistent max-width and padding for marketing pages, documentation, or content sites. Creates visual rhythm and prevents line-length issues. Standard building block for multi-section pages.
<dev-page-section theme="light">
<h2 style="margin: 0px">Deployments</h2>
<p style="margin: 0px">Ship production ready releases with review gates.</p>
</dev-page-section>Attributes:
theme-light | dark(default:light) - Token palette applied inside the shadow DOM.max-width-string(default:1280px) - Content max width.padding-string(default:80px 16px) - Outer padding for the section.
Slots:
default- Section content; typically headings and layout primitives.
Application layout with persistent side navigation and main content area. Essential for dashboards, admin panels, documentation sites, or settings pages. Supports collapse/expand for mobile. Maintains scroll positions independently for nav and content.
<dev-sidebar theme="light">
<a slot="nav" data-active="true" href="#overview">Overview</a>
<a slot="nav" href="#usage">Usage</a>
<a slot="nav" href="#settings">Settings</a>
<dev-card theme="light">
<p>Page content lives here.</p>
</dev-card>
</dev-sidebar>Attributes:
theme-light | dark(default:light) - Token palette applied inside the shadow DOM.collapsed-boolean(default:false) - Collapses nav to compact width.width-string(default:260px) - Expanded sidebar width.
Slots:
nav- Navigation links for the sidebar column.default- Main content area.
Events:
dev-sidebar:toggle-CustomEvent<{ collapsed: boolean }>- Emitted when the sidebar collapse state changes.
Empty vertical space component for precise spacing control. Use when layout components (Stack, Grid) don't provide enough control. Helpful for one-off spacing adjustments or maintaining design system spacing between disparate components.
<div>
<dev-card theme="light">
<p>Block above spacer</p>
</dev-card>
<dev-spacer theme="light" size="24px"></dev-spacer>
<dev-card theme="light">
<p>Block below spacer</p>
</dev-card>
</div>Attributes:
theme-light | dark(default:light) - Token palette applied inside the shadow DOM.size-string(default:24px) - Height of the spacer block.
Slots:
default- None; spacer renders empty block.
Two-column layout with customizable width ratio. Perfect for content+sidebar, image+text, or form+preview layouts. Ratio is adjustable (1fr 1fr for equal, 2fr 1fr for 2:1, etc). Stacks vertically on mobile for responsive behavior.
<dev-split theme="light" ratio="2fr 1fr">
<dev-card theme="light">
<p>Primary content area.</p>
</dev-card>
<dev-card theme="light">
<p>Side content.</p>
</dev-card>
</dev-split>Attributes:
theme-light | dark(default:light) - Token palette applied inside the shadow DOM.gap-string(default:24px) - Gap between columns.ratio-string(default:1fr 1fr) - Grid template columns value.
Slots:
default- Two (or more) children to place in the grid.
Vertical layout with consistent spacing between children. Most common layout primitive for forms, card content, or any vertically stacked elements. Controls gap and horizontal alignment. Preferred over manual margins for maintainable spacing.
<dev-stack theme="light" gap="1rem" align="start">
<dev-button theme="light" variant="primary">Primary</dev-button>
<dev-button theme="light" variant="secondary">Secondary</dev-button>
<dev-button theme="light" variant="ghost">Ghost</dev-button>
</dev-stack>Attributes:
theme-light | dark(default:light) - Token palette applied inside the shadow DOM.gap-string(default:16px) - Vertical spacing between children.align-stretch | start | center | end(default:stretch) - Cross-axis alignment.
Slots:
default- Child nodes stacked vertically.
Fixed bottom bar that remains visible while scrolling. Use for persistent CTAs (chat support, cookie consent), form actions, or mobile navigation. Ensures important actions are always accessible. Consider impact on mobile viewports.
<dev-sticky-footer theme="light">
<span>Need a hand? Chat with support.</span>
<div slot="actions">
<dev-button theme="light" variant="secondary" size="sm">Later</dev-button>
<dev-button theme="light" size="sm">Contact</dev-button>
</div>
</dev-sticky-footer>Attributes:
theme-light | dark(default:light) - Token palette applied inside the shadow DOM.
Slots:
default- Left-aligned content.actions- Right-aligned action buttons.
Container with styled scrollbars and max-height/width constraints. Use for logs, code output, chat histories, or any content that may overflow. Provides consistent cross-browser scrollbar styling. Prevents layout breaking from oversized content.
<div style="max-width: 500px">
<dev-scroll-area theme="light" max-height="200px" orientation="vertical">
<div style="padding: 1rem">
<h3 style="margin: 0px 0px 0.75rem 0px">Scrollable Content</h3>
<p style="margin: 0px 0px 0.5rem 0px">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore
magna aliqua.
</p>
<p style="margin: 0px 0px 0.5rem 0px">
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
</p>
<p style="margin: 0px 0px 0.5rem 0px">
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
</p>
<p style="margin: 0px">
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
</p>
</div>
</dev-scroll-area>
</div>Attributes:
theme-light | dark(default:light) - Token palette applied inside the shadow DOM.orientation-vertical | horizontal | both(default:vertical) - Scroll direction(s).max-height-string(default:"") - Maximum height for vertical scrolling.max-width-string(default:"") - Maximum width for horizontal scrolling.
Slots:
default- Scrollable content.
Keyboard-first command palette (Cmd/Ctrl+K) for power users. Inspired by VS Code, Slack, Linear. Enables fast navigation and actions without mouse. Perfect for developer tools, productivity apps, or complex dashboards. Supports fuzzy search and keyboard shortcuts.
<div>
<dev-button id="cmdPaletteBtn" theme="light" variant="secondary" size="sm">Open Command Palette</dev-button>
<dev-command id="cmdPalette" theme="light" placeholder="Type a commandβ¦">
<div
data-command-item=""
data-id="new"
data-label="Create new file"
data-icon="π"
data-group="File"
data-shortcut="Cmd+N"></div>
<div
data-command-item=""
data-id="save"
data-label="Save file"
data-icon="πΎ"
data-group="File"
data-shortcut="Cmd+S"></div>
<div
data-command-item=""
data-id="search"
data-label="Search files"
data-icon="π"
data-group="Edit"
data-shortcut="Cmd+P"></div>
<div data-command-item="" data-id="settings" data-label="Open settings" data-icon="βοΈ" data-group="View"></div>
</dev-command>
</div>
<script>
window.addEventListener("DOMContentLoaded", () => {
const palette = document.getElementById("cmdPalette");
const openBtn = document.getElementById("cmdPaletteBtn");
if (palette && palette.setItems) {
palette.setItems([
{ label: "Create new file", shortcut: "Cmd+N" },
{ label: "Save file", shortcut: "Cmd+S" },
{ label: "Search files", shortcut: "Cmd+P" },
{ label: "Open settings" },
]);
}
if (openBtn && palette) {
openBtn.addEventListener("click", () => {
palette.open = true;
});
}
});
</script>Attributes:
theme-light | dark(default:light) - Token palette applied inside the shadow DOM.open-boolean(default:false) - Controls visibility of the command palette.placeholder-string(default:Type a command…) - Placeholder text for the search input.
Slots:
<div data-command-item>- Command items with data attributes (id, label, icon, group, shortcut).
Events:
dev-command:select-CustomEvent<{ item: HTMLElement }>- Emitted when a command item is selected.
Right-click contextual actions menu. Provides native-feeling interactions for file managers, kanban boards, or content editors. Shows relevant actions based on selected item. Includes keyboard shortcuts display for discoverability.
<div>
<div
id="ctxTarget"
style="
padding: 2rem;
border: 2px dashed rgb(46, 125, 110);
border-radius: 0.5rem;
text-align: center;
cursor: default;
">
Right-click me to open context menu
</div>
<dev-context-menu id="ctxMenu" theme="light"></dev-context-menu>
</div>
<script>
window.addEventListener("DOMContentLoaded", () => {
const ctxMenu = document.getElementById("ctxMenu");
const ctxTarget = document.getElementById("ctxTarget");
if (ctxMenu && ctxTarget && ctxMenu.setItems && ctxMenu.setTrigger) {
ctxMenu.setItems([
{ label: "Copy", icon: "π", shortcut: "Cmd+C" },
{ label: "Cut", icon: "βοΈ", shortcut: "Cmd+X" },
{ label: "Paste", icon: "π", shortcut: "Cmd+V" },
{ separator: true },
{ label: "Delete", icon: "ποΈ", shortcut: "Del" },
]);
ctxMenu.setTrigger(ctxTarget);
}
});
</script>Attributes:
theme-light | dark(default:light) - Token palette applied inside the shadow DOM.open-boolean(default:false) - Controls visibility of the menu.
Slots:
default- No slots; items controlled via setItems() and setTrigger() methods.
Events:
dev-context-menu:item-select-CustomEvent<{ item: HTMLElement }>- Emitted when a context menu item is selected.
Draggable split pane with user-adjustable sizing. Essential for code editors, file browsers, or any interface where users want control over panel widths. Persists sizes to localStorage. Respects min/max constraints. Horizontal or vertical orientation.
<div style="height: 400px; border: 1px solid rgb(221, 221, 221); border-radius: 0.5rem; overflow: hidden">
<dev-resizable theme="light" size="300" min-size="150" max-size="500" orientation="horizontal">
<div slot="panel" style="padding: 1rem; background: rgb(245, 241, 234); height: 100%">
<h3 style="margin: 0px 0px 0.5rem 0px">Resizable Panel</h3>
<p style="margin: 0px; font-size: 0.875rem; color: rgb(111, 102, 88)">
Drag the handle to resize this panel. Min: 150px, Max: 500px.
</p>
</div>
<div style="padding: 1rem; height: 100%">
<h3 style="margin: 0px 0px 0.5rem 0px">Main Content</h3>
<p style="margin: 0px; font-size: 0.875rem">This area expands as you resize the left panel.</p>
</div>
</dev-resizable>
</div>Events:
dev-resizable:resize-CustomEvent<{ width: number; height: number }>- Emitted during resize operations.dev-resizable:resize-end-CustomEvent<{ width: number; height: number }>- Emitted when resize operation completes.
Flexible list item with icon, label, description, and trailing content. Building block for settings lists, preference panels, or feature toggles. Supports selected and disabled states. Use for structured lists with consistent formatting.
<div style="display: grid; gap: 0.5rem; max-width: 400px">
<dev-item theme="light" label="Email Notifications" description="Receive updates via email" icon="π§"></dev-item>
<dev-item theme="light" label="Push Notifications" description="Get alerts on your device" icon="π" selected="">
</dev-item>
<dev-item theme="light" label="SMS Notifications" description="Text message alerts" icon="π¬" disabled=""></dev-item>
<dev-item theme="light" label="Digest Mode" description="Weekly summary of activity" icon="π">
<span slot="trailing" style="font-size: 0.75rem; color: rgb(46, 125, 110); font-weight: 600">NEW</span>
</dev-item>
</div>Events:
dev-item:click-CustomEvent<{ value: string; disabled: boolean }>- Emitted when the item is clicked.
The design system includes two unified color palettes:
- Light Theme - Clean white backgrounds with dark code blocks
- Dark Theme - Pure black backgrounds with minimal elevation
- Unified Accent - Clean blue (#0EA5E9) across both themes for consistency
- Sans-serif: Inter/SF Pro system fonts
- Monospace: JetBrains Mono/Fira Code for code
- Scales: From 12px (xs) to 64px (7xl)
- Weights: 400-800 range
Consistent spacing scale from 4px to 128px based on the 80-120px section rhythm from the design analysis.
All components extend BaseComponent which provides:
- Shadow DOM encapsulation
- Theme attribute handling
- Lifecycle management
- Helper methods for rendering
Components are built with:
- Native Custom Elements API
- TypeScript for type safety
- CSS-in-JS via template literals
- No external dependencies
- Modern browsers with Custom Elements v1 support
- Chrome/Edge 67+
- Firefox 63+
- Safari 10.1+
MIT