@archetypeai/ds-ui-svelte-console
v0.10.0
Published
Archetype AI Design System Console Primitives (Svelte 5 + ds-lib-tokens Integration)
Readme
@archetypeai/ds-ui-svelte-console
Console primitives of the Archetype AI design system. Svelte 5 primitives built on bits-ui, styled with Tailwind v4 and tailwind-variants, and wired to the @archetypeai/ds-lib-tokens theme. TypeScript source ships as .svelte files (no precompilation) so consumers get full type-checking and IDE go-to-definition.
The console flavor ships two ways from the same source, with different roles:
- npm package (this document's default) - how apps consume primitives: they stay in
node_modules, imported per-subpath, updated by version bump. The package is the source of truth. - shadcn-svelte registry - how primitives get modified or extended: pull a primitive's editable source into your app, change it, then port the change back into the package. See Modifying a primitive (registry workflow).
Install
npm i @archetypeai/ds-ui-svelte-consoleThen install all peer dependencies:
npm i svelte tailwindcss bits-ui tailwind-variants tailwind-merge clsx @lucide/svelte @archetypeai/ds-lib-tokens svelte-sonner shiki yaml @cfworker/json-schemaTailwind v4 setup (mandatory)
Tailwind v4 does not scan node_modules by default. Without the @source directive below, every class in this package is purged at consumer build time and components render unstyled - this is the single most common silent failure.
In your consumer's app.css, in this order:
@import 'tailwindcss';
@import '@archetypeai/ds-lib-tokens/theme.css';
@source "../node_modules/@archetypeai/ds-ui-svelte-console/dist";Order matters:
tailwindcssregisters the engine.@archetypeai/ds-lib-tokens/theme.cssdeclares the CSS variables that component classes consume (atai-neutral,bg-card,stroke-icon-default, etc.).@sourcemakes Tailwind scan this package's.sveltefiles so utility classes used inside the package are emitted into your consumer bundle.
Adjust the @source path if your app.css lives elsewhere - it must resolve to the package's dist/ directory.
TypeScript setup
In your consumer's tsconfig.json (or jsconfig.json), set:
{
"compilerOptions": {
"moduleResolution": "bundler"
}
}"node16" and "nodenext" also work. Without one of these, the types condition in this package's exports map is bypassed, types fall back to the default JS file, and editor features degrade.
Usage
Each primitive lives at its own subpath. There is no barrel import { Button } from '@archetypeai/ds-ui-svelte-console' - subpaths keep svelte-sonner, shiki, yaml, and @cfworker/json-schema out of bundles for consumers who never touch the codeblock or sonner primitives.
<script lang="ts">
import { Button } from '@archetypeai/ds-ui-svelte-console/primitives/button'
import { Badge } from '@archetypeai/ds-ui-svelte-console/primitives/badge'
let buttonRef: HTMLButtonElement | null = $state(null)
</script>
<Badge variant="default">Hello</Badge>
<Button bind:ref={buttonRef} variant="primary" size="md">Click me</Button>Per-primitive subpaths
| Primitive | Import path |
|---|---|
| Alert | @archetypeai/ds-ui-svelte-console/primitives/alert |
| Badge | @archetypeai/ds-ui-svelte-console/primitives/badge |
| Button | @archetypeai/ds-ui-svelte-console/primitives/button |
| Card | @archetypeai/ds-ui-svelte-console/primitives/card |
| Checkbox | @archetypeai/ds-ui-svelte-console/primitives/checkbox |
| CodeBlock | @archetypeai/ds-ui-svelte-console/primitives/codeblock |
| Collapsible | @archetypeai/ds-ui-svelte-console/primitives/collapsible |
| Dialog | @archetypeai/ds-ui-svelte-console/primitives/dialog |
| DropdownMenu | @archetypeai/ds-ui-svelte-console/primitives/dropdown-menu |
| DropZone | @archetypeai/ds-ui-svelte-console/primitives/dropzone |
| EmptyState | @archetypeai/ds-ui-svelte-console/primitives/empty-state |
| Input | @archetypeai/ds-ui-svelte-console/primitives/input |
| InputGroup | @archetypeai/ds-ui-svelte-console/primitives/input-group |
| Item | @archetypeai/ds-ui-svelte-console/primitives/item |
| Label | @archetypeai/ds-ui-svelte-console/primitives/label |
| Progress | @archetypeai/ds-ui-svelte-console/primitives/progress |
| Select | @archetypeai/ds-ui-svelte-console/primitives/select |
| Separator | @archetypeai/ds-ui-svelte-console/primitives/separator |
| Sonner | @archetypeai/ds-ui-svelte-console/primitives/sonner |
| Spinner | @archetypeai/ds-ui-svelte-console/primitives/spinner |
| Table | @archetypeai/ds-ui-svelte-console/primitives/table |
| Tabs | @archetypeai/ds-ui-svelte-console/primitives/tabs |
| Textarea | @archetypeai/ds-ui-svelte-console/primitives/textarea |
| Tooltip | @archetypeai/ds-ui-svelte-console/primitives/tooltip |
The cn() helper and shared TS type helpers (WithElementRef, WithoutChild, WithoutChildren, WithoutChildrenOrChild) are exported from @archetypeai/ds-ui-svelte-console/primitives/utils. The dark-mode controller lives at @archetypeai/ds-ui-svelte-console/primitives/theme (see below).
Composition patterns will arrive at @archetypeai/ds-ui-svelte-console/patterns/<name> in a later release without breaking any of the primitive paths above.
Dark mode (primitives/theme)
The package manages dark mode without any external dependency. darkMode is a reactive singleton that mirrors the dark class on <html>:
<script lang="ts">
import { darkMode } from '@archetypeai/ds-ui-svelte-console/primitives/theme'
</script>
<button onclick={() => darkMode.toggle()}>
{darkMode.current ? 'Switch to light' : 'Switch to dark'}
</button>darkMode.current- reactiveboolean,truewhile<html>carries thedarkclass. Safe to read in$derived/$effect.darkMode.set(value)- adds/removes the class. CSS transitions are suppressed for one frame during the swap, so the whole page flips at once instead of staggering per-element.darkMode.toggle()- convenience forset(!current).
A MutationObserver keeps current in sync even when something else toggles the class (e.g. an app-level theme store or SSR-rendered markup), and the module is SSR-safe - it no-ops without a document. Theme-aware primitives (e.g. CodeBlock's syntax highlighting) consume darkMode internally.
CodeBlock editor & validation
primitives/codeblock exports two components: CodeBlock (read-only, shiki-highlighted) and CodeBlockEditor (editable, with a YAML + JSON Schema validation pipeline):
<script lang="ts">
import { CodeBlockEditor } from '@archetypeai/ds-ui-svelte-console/primitives/codeblock'
let value = $state('name: my-config')
let valid = $state(true)
</script>
<CodeBlockEditor bind:value bind:valid schema={mySchema} showDownload downloadBaseName="config" />value(bindable) - the YAML text being edited.schema- optional JSON Schema object; when provided, the parsed YAML is validated with@cfworker/json-schema.valid(bindable) -truewhile the text parses and passes the schema.schemaUnavailable(bindable) -truewhen the schema itself can't be compiled into a validator.placeholder,showDownload,downloadBaseName- editor chrome.
Validation is debounced (250 ms) and errors are mapped back to source lines with human-readable messages and fix suggestions. The shiki highlighter is lazy-loaded and follows darkMode for its light/dark theme. This pipeline is why yaml and @cfworker/json-schema are peer dependencies.
API conventions
bind:reffor element handles. Every primitive exposesrefas a$bindableprop. Use<Button bind:ref={el} />to capture the underlying DOM element.data-*attributes are public API. Every variant slot carriesdata-slotplus variant attributes (data-variant,data-size,data-state,data-typography, etc.). Use them as descendant-selector hooks. Renaming requires a major version bump.- Composite primitives compose by sub-part. Card, Dialog, Item, Select, DropdownMenu, Tabs, Collapsible, Tooltip, InputGroup, and DropZone export their sub-parts (e.g.
Dialog.Trigger,Dialog.Content). Passclassto each sub-part directly - there is noheaderClass/contentClassshorthand on the root. childsnippet on Item is the bits-uiasChildrender-prop pattern, not a typo forchildren.- Variant configs are exported. Each
index.tsre-exports itstv()configs (e.g.buttonVariants,inputGroupButtonVariants) so consumers can compose variants without forking source.
Modifying a primitive (registry workflow)
The npm package is the source of truth for primitives - consume every primitive from it, and don't keep local forks or hand-roll one-offs. What you will hit is a primitive that needs to change: an internal refactor, a variant's styling adjusted, or the API extended with a new variant or prop axis. For that, the same primitives are served as a shadcn-svelte registry at https://design-system-console.archetypeai.workers.dev/, so you can work on the real source inside your app and upstream the result:
Pull the editable source into your app:
npx shadcn-svelte@latest add https://design-system-console.archetypeai.workers.dev/r/button.jsonComponents install flat under your
uialias -$lib/components/ui/{button,codeblock,...}/, plusutils.tsandtheme.svelte.tsat theuiroot. Cross-component dependencies are declared with the registry'slocal:prefix, so installing one component pulls everything it needs from the same registry - e.g.codeblockpullsbutton,spinner,theme, andconsole-utils.Make the change against the local source and validate it in-app.
Port the change back into
ds-ui-svelte/console/primitives/in the design-system repo and release. Then bump the package version in your app, delete the local copy, and import fromprimitives/<name>again. A copy living long-term inui/is drift - the package, not the app, owns primitives.
A genuinely new primitive follows the same path in reverse: build it in ui/ mirroring the existing primitives (a tailwind-variants config, cn from utils, data-slot/data-variant attributes, runes, token classes throughout - read an installed primitive's source as the template), then ship it into the package.
Notes:
- Always install via the full registry URL (or a project script wired to it - the console app exposes
npm run ds [<name>]for exactly this). A barenpx shadcn-svelte@latest add buttonresolves against the default shadcn-svelte registry and installs the generic upstream component, not the Archetype one. - The utils item is named
console-utils(shadcn-svelte reservesutils); it ships the consolecn()with therounded-interactivetailwind-merge extension. Without it, class merging on interactive radii silently breaks. /r/all.jsonlists all component URLs for programmatic access.- Registry-installed files don't need the Tailwind
@sourcedirective (they live in your repo and are scanned normally), but the@archetypeai/ds-lib-tokens/theme.cssimport remains mandatory.
Extending in labs
Console is the stable base tier: its API contract is frozen and its data-* attributes are public API that only a major version may change. When you need new or experimental behavior on top of a console primitive, build it in the labs tier (@archetypeai/ds-ui-svelte-labs) - never fork or mutate console to accommodate a one-off.
Labs adapts console without changing it, using an escape-hatch ladder - reach for the lowest rung that solves the problem:
classprop override. Every primitive forwardsclassto its root slot; pass Tailwind utilities to restyle in place.data-slot/data-*selectors in your CSS. Target the public attributes ([data-slot="..."],[data-variant="..."],data-state) with descendant selectors - no source changes.tv({ extend })on the exported variant cluster. Each primitive re-exports itstailwind-variantsconfig (e.g.buttonVariants); extend it to add a variant or override defaults without forking the component.- A labs wrapper component (rare). When the first three can't express it, compose the console primitive inside a labs primitive.
This ladder keeps the modification surface inside labs. Behavior that proves itself there graduates back into console in a deliberate, API-hardening change - the flow is always labs → console, never console reaching up into labs. See the labs README for the consumer-side view of the same ladder.
License
MIT. See LICENSE.
