# letbe-ds > Application-grade UI in plain CSS + JS — no build, no dependencies, no > framework. Tokens → components → templates, MIT. letbe-ds is a complete design system in three plain files — design tokens, component styles, one JavaScript runtime. No build step, no dependencies, no framework: link the files and you own the entire stack. A three-tier DTCG token architecture drives everything. From buttons, forms and cards it scales to the application tier: data tables, dashboards, kanban boards, calendars, media players, AI chat. Interactions are accessibility-first; heavy engines (data layers, virtualizers, streaming, AI models) are deliberately the consumer's choice. MIT-licensed, demo media included (brand and third-party assets excluded — see NOTICE). ## Runtime — three files, then opt-in modules Load order: `tokens/theme.css` → `components/components.css` → `js/lb.js`. Components auto-initialise from `data-lb-*` attributes on DOMContentLoaded. Heavier controllers are opt-in modules loaded AFTER `lb.js` (header, shell, board, chat, media, timeline, selection — see the README vendor manifest for the exact file list). If a `data-lb-*` root has no controller loaded, `lb.js` logs a console warning naming the missing script. Class combos are contracts: copy the full combo from each demo (base + variant + size, e.g. `lb-btn lb-btn--secondary lb-btn--medium`; every table cell carries `lb-table__cell`). Base classes alone render unstyled. ## Tokens — DTCG source, generated CSS `tokens/source-tokens.json` is authored in the W3C Design Tokens Community Group (DTCG) format — `$value`/`$type`, aliases as `{path.to.token}` — and is the single source of truth. `node scripts/build-tokens.js` generates `tokens/theme.css` (never edit it by hand). Three tiers: L1 primitives → L2 semantic (`--lb-bg-*`, `--lb-fg-*`, `--lb-surface-*`) → L3 component (`--lb-action-*`, `--lb-field-*`). L2/L3 never hold raw values, only references. Role prefixes name what the pixel IS, not the CSS property: `bg-*` = surfaces/fills (incl. value-display fills), `fg-*` = ink — text AND icons (icons draw with currentColor), `border-*` = boundaries and focus only; cross-role binding is forbidden outside documented cases. Native light/dark themes (`[data-theme="dark"]`), responsive S/M/L typography modes, ~500 tokens. The same tokens round-trip into Figma variables through the letbe plugin. Theming: layer your own stylesheet after `theme.css` and re-declare token values — the whole system re-themes, both modes, zero forks. Text on saturated fills uses `--lb-fg-inverse-strong` (theme-stable on-color role). ## Foundation (10) Token-level pages — vocabulary and scales, not classed components. - [Token Architecture](/token-architecture/): The reasoning behind the tokens: three tiers (DTCG source → generated CSS), the naming grammar, role prefixes (`bg` fills, `fg` ink — text AND icons, `border` boundaries), the property≠role principle, sanctioned cross-role exceptions, the value-display roles, the minting checklist, and the Figma round-trip via the letbe plugin. - [Typography](/typography/): Six semantic text roles — display, heading, body, action, label, caption — each with multiple sizes. Use display for hero moments, heading for section titles, body for paragraph content, action for interactive text (buttons, tabs), label for form field labels, caption for helper/metadata text. All styles come from `source-tokens.json` and scale with the theme editor's base size. - [Colors](/colors/): Six L1 color palettes — neutral, violet (brand), blue (info), green (success), yellow (warning), red (danger) — each with 11 steps from 50 (lightest) to 950 (darkest). Primitives are immutable; theming happens by remapping L2 semantic tokens to different L1 palettes. Pick brand color in the theme editor to generate a new `brand.*` palette. - [Icons](/icons/): 254 icons bundled as standalone SVGs in three categories. OUTLINED (Lucide, stroke-based, default) — navigation, actions, status, people, docs, AI-era set (sparkles, bot, brain, audio-lines, mic, circle-stop, loader-circle, paperclip…), dev/data (terminal, braces, git-branch, database…), media/music (shuffle, repeat, disc-3, podcast, mic-vocal, guitar…). FILLED — 13 solid status/rating glyphs. BRAND (Simple Icons + Bootstrap Icons, filled single-path logos, `fill: currentColor`) — the letbe house mark, social (tiktok, x-logo, instagram, facebook, linkedin, whatsapp, telegram, discord, reddit, threads, bluesky, mastodon, snapchat, pinterest, twitch, youtube), music/creator (spotify, apple-music, soundcloud, bandcamp, deezer, tidal, youtube-music, patreon, substack, medium), dev/design (github, gitlab, figma, slack, dribbble, behance, google, apple). Lucide 1.0 removed all brand icons, hence the dual sourcing; brand logos are trademarks — usage subject to each brand's guidelines (see assets/icons/LICENSE.txt). Use `LB.icon('name', element)` to inject by name; stroke width follows `--lb-stroke-icon` (outlined only — brand/filled color via fill). Requires serving over http (not `file://`). - [Size](/size/): L1 size scale — used across spacing, padding, gap, heights, widths, and (via reference) border-widths. Built on a 4px base grid with a 2px sub-grid for hairline details. Includes the theme-editable `--lb-size-theme` (1.6px default, backs `--lb-border-width-action`, runtime-mutable via the theme editor's Action stroke knob). - [Radius](/radius/): Corner radius primitives (L1) and semantic aliases (L2) for interactive, field, surface, overlay, badge, and full/pill shapes. Theme editor's corner-radius knob remaps the L2 aliases to different L1 values so all components follow one slider. - [Shadow](/shadow/): Elevation shadows. L1 shadow.1–4 define progressively deeper drop-shadows; L2 aliases (card, dropdown, modal, tooltip, focus) point at the right primitive for each surface role. The focus shadow (L1 shadow.ring) is available for inset focus indicators; no shipped component uses it — focus states are drawn with outline instead. - [Opacity](/opacity/): Opacity scale for transparent overlays and muted elements. L1 provides 0, 0.05, 0.1, 0.2, 0.4, 0.6, 0.8, 1. L2 names them (hidden, faint, subtle, light, medium, heavy, strong, opaque) for semantic use in interactive states. - [Animation](/animation/): Motion tokens — durations (0–500ms) and easing curves. L2 aliases give semantic names like `dur-fast` (100ms, state changes), `dur-normal` (150ms, menus/tooltips), `dur-gentle` (200ms, transitions), `ease-ui` (standard UI), and `ease-bounce` (playful spring). Honors `prefers-reduced-motion`. - [Layout](/layout/): Four single-purpose layout primitives covering ~80% of the spacing patterns we use: **Stack** (vertical, gap), **Cluster** (horizontal, gap, wraps) with --start/--end/--center/--between alignment modifiers, **Grid** (auto-fit columns with --min and --gap), and **Masonry** (Pinterest-style packed columns for variable-height items). Each accepts CSS custom properties so the same class works for tight toolbars and breathing-room hero sections. Replaces inline `style="display: flex; gap: …"` everywhere — composes freely (Stack of Clusters is the form-row pattern; Grid of Cards is the dashboard pattern). ## Charts (8) Hand-rolled SVG / pure CSS. No charting library; data via `data-lb-*` attributes or JS options. - [Charts](/charts/): The data visualization suite — Stat, Sparkline, Bar List, Donut, Bar Chart, and Line Chart. All hand-rolled SVG (or pure CSS), no charting libraries, no dependencies. Built around the `--lb-data-1..8` palette tokens so every chart adopts the active theme automatically. Compose with Card for surface, Grid for KPI strips, Table for tabular adjacency, Resizable for dashboard layouts. - [Bar Chart](/bar-chart/): Categorical bar chart — vertical (default) or horizontal, single or multi-series, grouped or stacked. Hand-rolled SVG. Computes nice round-number axis ticks (port of d3-array's algorithm in ~30 lines, no dep). Hover tooltip on each bar via the shared chart tooltip pattern. Auto-renders a legend for multi-series. Color picks `--lb-data-1..8` by default. - [Bar List](/bar-list/): Ranked horizontal bar list — top countries, top sources, top pages. Each row is a label + value with a proportional bar layer behind. Pure HTML/CSS — JS only computes the per-row width as a percentage of the largest value. Drops into Card or any container; consumer can pair labels with `` for clickable rows by adding `--interactive`. The bar color picks `--lb-data-1` by default; override per-list via `style="--color: …"`. - [Dashboard](/dashboard/): A worked composition putting the whole Charts suite in context. Resizable shell with Tree navigation rail on the left, a stacked main column with a KPI strip (Stat × Sparkline × Card × Grid), a Line Chart panel, a Bar Chart + Donut row, and a Table + Bar List adjacency, plus a right rail with compact Stats and a uptime Donut. No application code — every part is letbe-ds primitives composing. PLUS the Store overview commerce skeleton: commerce and analytics dashboards read best as KPI strip → big time-series → breakdowns → table. Filter row (Popover dropdowns + --pulse live badge), 4 KPI Stat cards with sparklines, revenue Line Chart with the DASHED comparison series (previous period as context), channel Donut · top-products Bar List · goal ring (Donut center slot), horizontal + vertical axis Bar Chart cards, recent-customers Table. - [Donut](/donut/): Proportions chart. Hand-rolled SVG using same-radius circles with `stroke-dasharray` arcs and per-segment rotation. Configurable thickness (set thickness to 50 for a pie). Optional center slot for value + label. Pairs with the Donut Legend list for a side-by-side breakdown. Color picks from `--lb-data-1..8` by default; override via the `colors` array in the JSON config. - [Line Chart](/line-chart/): Time-series chart — single or multi-series, line or area fill. Hand-rolled SVG. Reuses the same niceTicks helper as Bar Chart for round-number axis ticks. Per-point dot markers act as hover targets driving a unified multi-series tooltip (all series at the hovered x position). Per-series `"dashed": true` renders the comparison-overlay idiom (dashed stroke for a de-emphasized previous-period series; pair with a muted color). Auto-renders a legend for multi-series. Color picks `--lb-data-1..8` by default. - [Sparkline](/sparkline/): Inline mini chart — line, area, or bar — with no axes, no legend, no chrome. Hand-rolled SVG with a 100-unit viewBox so it scales fluidly to its host's CSS size; no resize observer needed. Default color picks `--lb-data-1`; override per-instance via `data-lb-color` or `style="--color: …"`. Optional end-of-series dot for the metric-card pattern. ARIA: rendered as `role="img"` with a generated label summarising point count and trend direction. - [Stat](/stat/): KPI primitive — label, big number, optional delta indicator, optional caption. Optional leading `.lb-stat__icon` in the label row (opt-in markup, aria-hidden, always fg-muted — the delta badge stays the only semantically-colored glyph; default is icon-free; icons are scan anchors for LARGE mixed-metric grids only, and it is all tiles in a grid or none). Pure HTML/CSS, no JS. Pairs with Sparkline for the metric-card pattern; stack several inside a Grid for a KPI strip. Owns no surface — drop inside Card or any container. The delta is just a Badge (small variant) inside `.lb-stat__sub`: success tone with `trending-up` icon for gains, danger tone with `trending-down` for losses, default tone with `minus` for flat — reusing the system's status palette and icons rather than bespoke chip styling. ## Components (54) Each page documents markup, variants, a11y behavior and JS API. - [Accordion](/accordion/): Collapsible panels for progressively disclosing content. Use when you have grouped information that doesn't need to be visible at once — FAQ lists, settings sections, nested details, filter groups. Avoid for primary navigation; use tabs or nav instead. Each item supports an optional leading icon (`.lb-accordion__icon`) and a right-cluster slot (`.lb-accordion__indicators`) for badges + chevron. - [Avatar](/avatar/): User or entity representation with image fallback to initials. Three sizes: small, medium, large. Use in user lists, comments, nav user-menus, chat messages. Initials render automatically when the image fails to load. The same page covers **Avatar Group** — stacked overlapping avatars with optional '+N' counter and hover-to-fan-out for teams, assignees, attendees. - [Badge](/badge/): Compact status label, and the read-only counterpart to Chip — use `--default` for neutral non-status descriptors. Four variants: default (brand), success, warning, danger. Two sizes: medium (default, 16px icon slot) and small (12px icon slot). Supports an optional leading dot (`.lb-badge__dot`) or icon (`.lb-badge__icon`) — useful for money-flow and a11y-forward statuses. - [Banner](/banner/): Contextual message spanning the top of a section or page. Four status variants (info, success, warning, danger), three sizes (small, medium, large). Supports optional CTA actions and dismiss. Use for page-level announcements, system status, or contextual help. - [Breadcrumb](/breadcrumb/): Hierarchical navigation showing the user's location in the app. Use when pages are nested three or more levels deep. The current page gets `aria-current="page"` and is not a link. - [Button](/button/): Primary action trigger. Variants: primary (main action), secondary (alternative action, outlined), subtle/ghost (low emphasis), danger. Sizes: medium, small. Supports leading icon (`.lb-btn__icon`), trailing count badge (`.lb-counter.lb-counter--on-btn` — auto-hides when empty), or icon-only (`.lb-icon-btn` with `aria-label`). Includes a Button Group layout wrapper for aligning multiple buttons (start, end, center, justify, stack). - [Calendar](/calendar/): Event & booking calendar with three views (Month / Week / List) and two intents (browse / book). Sits in the data-viz family alongside Bar Chart and Stat — different from Datepicker (input control). ISO 8601 week numbers in the left column, today-marked with an underline accent, past days dimmed for clear what-has-vs-hasn't-happened cue. Events colour via the L2 categorical palette (`data.1..8`). All values route through DS tokens; no hardcoded colours. - [Card](/card/): Surface container for grouped content. Default is bordered with 24px padding. Variants: `--elevated` (shadow), `--interactive` (hover lift + brand border + focus ring), `--stat` (dashboard metric tile with big number + label + delta). Sizes: `--sm` / default / `--lg`. Slots: `__header` (title + actions), `__body`, `__section` (divided subsection), `__footer`, `__media`, `__nav-icon` (trailing chevron / arrow for navigation cards). Media positions: top / left (horizontal) / background (full-bleed overlay, text pinned to white regardless of theme). States: `--selected`, `--disabled`. - [Chat](/chat/): AI Chat component set — Bubble, Thread, Composer, ToolCall, ConvList, ContextBudgetBar, AI Chat template, Artifact panel. letbe-ds ships chrome + layout + state machine; consumers bring the LLM SDK, streaming source, markdown engine, sandboxed previews, and tool implementations. Per the pluggable-engine HARD rule, nothing in lb-chat*.js wraps a model API. Modules live in js/components/lb-chat*.js so pages opt in to only what they use. Slices 1-7 ship the primitives. Slice 8 adds `LB.AI Chat template`. Slice 9 adds `LB.Artifact` (right-rail panel via Tabs + Select + lb-code-block). Slice 10 lands the accessibility + keyboard pass: Thread is `role=log aria-live=polite`, streaming bubbles carry `aria-busy=true` until done; ConvList is `role=listbox` with `role=option` rows + arrow-key roaming + Home/End; kebab menu has full menu-keyboard support (Arrow / Home / End / Enter / Esc / Tab) + focus management + `aria-haspopup` + `aria-expanded`; Composer textarea gets an auto `aria-label`; AI Chat template exposes customisable keyboard shortcuts via `data-lb-chat-shortcuts` JSON or `setShortcuts(map)` — defaults: `/` focus composer, `mod+k` new chat, `mod+r` regenerate, `mod+\` toggle sidebar; unknown handler names emit a generic `lb-chat-workspace-shortcut {handler}` event for consumer-defined actions. Demo dashboard at /chat/workspace/. Slice 11 ships the composer popover variant + Timeline composition: `LB.openComposerPopover(opts)` builds a compact `LB.Composer` inside an `.lb-popover` surface anchored to a rect. LB.Timeline gains an `lb-timeline-selection` event on drag-end. Slice 12 closes the 12-slice arc with the two remaining workspace layouts (the code-assistant layout and the timeline/generation layout), the workspace dashboard at /chat/workspace/ updated to load all three layouts with `?layout=converse|dev|timeline` deep linking, plus the chat module pattern documented in `js/components/README.md`. A post-arc refinement pass relocated chrome based on user feedback: layout switcher moved from the header into the sidebar above the search row (high-discoverability), context-budget bar moved from the header to the foot of main (just below the composer), the sidebar settings dock was removed in favor of a workspace-wide Settings panel that opens in the rail via the new gear icon in the header (`ws.openSettings() / closeSettings() / toggleSettings()` with `addSettingsSection({id,label,icon,render})` for consumer extensions; sections ship with Appearance/Keyboard shortcuts/About). Rail is now multi-tenant — opening Settings closes any open Artifact panel and vice versa, and either tenant's close button collapses the rail entirely. ConvList group headers gain top borders for clearer section separation. Full arc spec in /letbe-presentation/ai-chat-deep-analysis.html. - [Checkbox](/checkbox/): Binary or ternary selection control. Supports checked, unchecked, indeterminate, and disabled states. Use for multi-select lists, settings toggles (when a clear on/off isn't implied), and form agreements. For a single boolean with immediate effect, prefer Switch. - [Chip](/chip/): Compact, toggle-able tag. Unselected state uses the secondary action style; selected uses primary. Click to toggle selection; X icon appears when selected to deselect. Use for filter lists, tag selection, and multi-choice interfaces. Not for primary navigation. - [Code Block](/code-block/): Styled code surface for snippets, configs, theme exports. Optional header strip with language label, filename, line numbers, opt-in regex highlighter for js/ts/json/css/html/bash. Lightweight (~150 LOC of regex, no highlighting-library dependency). **Action row** (Slice 6) holds Copy / Save / Open / Apply: Copy is dual-format (writes both text/plain AND text/html with syntax-highlight preservation — paste into a rich-text editor and get formatted code, paste into a terminal and get plain text). Save downloads as a file with smart extension. Open and Apply are opt-in — emit `lb-code-open` / `lb-code-apply` events for consumers building dev-IDE surfaces. All actions are always-visible (no hover-gating, per forum feedback). Inline counterpart `.lb-code` for prose-level snippets. - [Color Picker](/color-picker/): Saturation × value surface plus hue and (optional) alpha sliders. Below the picker: a HEX / RGB / HSL format toggle that swaps the bottom input row between a single hex field and three numeric component fields. Hand-rolled, no dependencies. Reuses the existing Popover and Input components for the trigger-swatch dropdown and the text fields. Optional preset-swatch row, optional native EyeDropper button (Chrome / Edge — progressive enhancement, hidden when unsupported). Two layouts: inline picker, or popover-triggered swatch button. Full keyboard support — arrows on each surface, Enter on the hex field, native step on numeric fields. Emits `lb-color-change` with `hex`, `rgb`, `rgba`, `hsl`, alpha — pick the format your code wants. - [Command Palette](/command-palette/): Global ⌘K / Ctrl+K launcher. Jump to any page, action, or setting without leaving the keyboard. Composes the List primitive in filterable mode for the command list, wrapped in a top-anchored modal shell with grouped commands, icons, and kbd-shortcut hints. - [Counter](/counter/): Numeric notification pill — the small bubble used by Tabs, Accordion, Calendar cells, and Button. Single component shared by all four hosts so the shape, sizing, and contrast guarantees stay consistent. Default is brand-themed (theme-stable saturated bg + theme-stable white digit) so contrast holds in light and dark themes and follows the user's brand colour. Variants: `--subtle` (dimmed for low-priority states), `--on-btn` (inherits the host button's variant colour pair). Auto-hides when empty. - [Date Picker](/datepicker/): Calendar date selection. Supports single-date and range modes; available inline or in a popover. Month/year navigation via custom selects. Use when the user needs to pick a specific day (booking, scheduling, filtering). For less precise time inputs prefer a standard input with format hint. - [Divider](/divider/): Horizontal or vertical rule separating content. Use between logically distinct sections or between items in a list. Prefer whitespace over dividers when the layout makes groupings clear on its own. - [Dropdown](/dropdown/): Combobox input — type to filter, pick from a list. Use when the option set is large (>10 items) and the user knows roughly what they want. For small option sets use Select instead. For free-form text, use a regular Input. - [Empty State](/empty-state/): Reusable 'no data yet' placeholder for list views, search results, filtered tables, dashboards, inbox-zero screens, 404s, and error states. Slots: icon (48px default), title, description, action buttons. Three sizes: `--sm` (inline), default, `--lg` (full-page). Optional `--danger` modifier for error states (failed to load, permission denied) — tints the icon red while keeping the rest of the state neutral. Nine demo patterns ship: inbox-zero, no-results, 404, no-icon, title-only, in-card, onboarding ("create your first"), filtered-empty, error-state. - [File Uploader](/file-uploader/): Drag-drop zone + click-to-browse native input + uploaded-files list with progress bars and remove controls. Constraints: `data-lb-accept` (MIME/extension list), `data-lb-max-size` (bytes), `data-lb-max-files`. Single-file mode by default unless `data-lb-multiple` or the inner input has `multiple`. Compact variant for settings rows. - [Flag](/flag/): Country flag with optional code or name label. Uses the same vendored flag set as the Phone input (173 ISO-coded SVGs at `/assets/flags/`). Three sizes (small / medium / large). Default circular shape matches Phone; `--rect` flips to a rectangular thumbnail for table rows where a non-circular icon reads more naturally. Pairs with Bar List, Bar Chart, Table, and Stat for compact country representation in dashboards and analytics views. - [Footer](/footer/) [app-shell program stage 2]: Site/app footer — pure CSS, no JS. Two flavors from one component: **marketing** (default: brand zone with logo + tagline + social row, 3–5 link columns, optional newsletter, legal row) and **app** (`--app`, one thin row: copyright + links + optional status badge). Optional `__giant` decorative oversized brand wordmark (aria-hidden). Consumes `.lb-btn`, `.lb-input-wrap` (newsletter), `.lb-badge` (status), `.lb-icon-btn` (social links), and the brand icon set. Newsletter placement is a variant: inline beside the columns, or `--row` as its own full-width band. Mobile collapse is plain stacking — no accordion (a11y cost, no verified benefit). a11y (verified, W3C/MDN): must be the page's single body-scoped `