cinder
Components for product interfaces.
The core package is @lostgradient/cinder: accessible primitives, domain-suite components, design-system tokens, per-component CSS, generated prop schemas, and examples that can be read by people or tooling. The Chat domain suite is published separately as @lostgradient/chat, with Cinder and Svelte supplied by the consuming application as peer dependencies. Chat's own conversation-model dependency (conversationalist) ships with it — the consuming application never installs that directly.
Use Cinder when you want UI building blocks without adopting a framework-level router, form-state manager, data fetching layer, global state provider, or theme provider. The package is SSR-safe.
Install
bun add @lostgradient/cinder svelte
svelte is a peer dependency; Cinder targets Svelte >=5.56.0 <6.
lucide-svelte is not a peer — Cinder owns it as a pinned regular dependency and installs it
for you. That pin is deliberate: Cinder ships a prebuilt SSR bundle, so a consumer resolving a
different lucide-svelte version on the client than the one baked into that bundle produces
structurally different markup for the same icon and a hydration_mismatch on first load. Cinder
uses Lucide for its own component chrome and does not provide a general icon library for your
application-specific icons — install Lucide yourself if you want it for your own components.
Install the Chat domain suite separately when you need it, alongside the Cinder install above:
bun add @lostgradient/chat @lostgradient/markdown
@lostgradient/markdown is a required peer of Chat — its markdown-preview component dynamically imports @lostgradient/markdown/rendering to render message bodies, so it must be installed alongside Chat itself, not treated as optional. Chat's conversationalist conversation model (and its zod dependency) ships as a regular dependency of @lostgradient/chat — it installs automatically and you never add it yourself.
Install the Editor domain suite separately for MarkdownEditor, ReviewEditor, and DiffViewer:
bun add @lostgradient/editor
See @lostgradient/editor's README for its own peer install list
(Milkdown/ProseMirror and @lostgradient/markdown).
Cinder no longer exposes any ./markdown/*, ./editor/*, or ./commentary/* subpath — that
surface moved to the two published sibling packages above during the
package-boundaries extraction. If your app imports one
of those removed subpaths, install @lostgradient/markdown directly (bun add @lostgradient/markdown) and update the import specifier — see
@lostgradient/cinder's README for the full removed-subpath
migration table. Syntax-highlighting (CodeBlock automatic highlighting via shiki) needs no
separate install — shiki, @shikijs/engine-oniguruma, and @shikijs/types are regular
dependencies of Cinder and install automatically.
Quickstart
Load the base stylesheet once at your app entry, before importing any cinder component:
import '@lostgradient/cinder/styles';
import '@lostgradient/cinder/styles/guard';
@lostgradient/cinder/styles declares the cascade-layer order, design tokens, foundation rules, utilities, and shared internal chrome. @lostgradient/cinder/styles/guard is development-only; it warns when the base stylesheet is missing or loaded too late.
Then import components normally. In browser/Svelte-aware builds, component entry points automatically include their co-located CSS:
<script lang="ts">
import Button from '@lostgradient/cinder/button';
import Modal from '@lostgradient/cinder/modal';
</script>
<Button variant="primary" label="Save changes" />
<Modal open title="Edit project" />
If you want one stylesheet with everything, use:
import '@lostgradient/cinder/styles/all';
That bundle includes the base stylesheet plus every component stylesheet. It is convenient, but it is not tree-shaken. The per-component /styles subpaths remain available for consumers that intentionally manage CSS imports themselves.
Import Shapes
Prefer subpath imports in browser applications:
import Button from '@lostgradient/cinder/button';
import Modal from '@lostgradient/cinder/modal';
The root barrel is available when convenience matters more than keeping the import graph narrow:
import { Button, Modal } from '@lostgradient/cinder';
Every component subpath carries types, svelte, node, and default export conditions. Svelte-aware tooling reads the source entry through the svelte condition; plain Node SSR reads the compiled server entry through node; other non-Svelte ESM consumers can resolve the compiled default entry.
Finding Components
Cinder ships a machine-readable manifest:
import manifest from '@lostgradient/cinder/manifest' with { type: 'json' };
manifest.components[] lists each public component with its id, category, status, tags, purpose, useWhen, avoidWhen, related components, and artifact subpaths.
Every component also ships generated sidecars:
import buttonSchema from '@lostgradient/cinder/button/schema';
import buttonVariables from '@lostgradient/cinder/button/variables';
import buttonConstraints from '@lostgradient/cinder/button/constraints' with { type: 'json' };
import buttonExamples from '@lostgradient/cinder/button/examples' with { type: 'json' };
schema: JSON Schema for props.variables: public CSS custom properties for the component.constraints: cross-prop rules that JSON Schema cannot express cleanly.examples: canonical runnable usage snippets.
Not every component has constraints or examples; check the manifest's hasConstraints and hasExamples flags before importing those subpaths.
Styling and Theming
Cinder uses CSS custom properties and color-scheme; there is no provider to mount.
html {
color-scheme: light;
}
html[data-theme='dark'] {
color-scheme: dark;
}
[data-theme='light'] {
color-scheme: light;
}
[data-theme='dark'] {
color-scheme: dark;
}
Use [data-theme='dark'] or [data-theme='light'] on a subtree for scoped theme islands. Cinder pins the core semantic surface, text, border, overlay, interaction, status, and control tokens inside those scopes so components inherit local values without app-level token overrides. If your app replaces Cinder's public tokens for custom branding, set those brand overrides in the same scoped theme selector.
Public tokens use the --cinder- prefix. Internal implementation variables use --_cinder-; do not redefine those.
See:
What Cinder Does Not Provide
Cinder is a presentation library, not an application framework.
- No router.
- No form-state manager.
- No application-wide toast singleton; use
ToastRegionfor scoped toast UI. - No data fetching layer.
- No global state provider.
- No general-purpose icon library for product-specific icons.
Wire those pieces with your application stack and pass the resulting state, callbacks, links, icons, and data into Cinder components.
Workspace Layout
This repository is a Bun workspace.
| Workspace | Purpose |
|---|---|
packages/components |
Published @lostgradient/cinder package. |
packages/playground |
Private component playground and static export. |
packages/testing |
Private Playwright, axe, and visual-regression harness. |
packages/markdown |
Published @lostgradient/markdown package (headless Markdown pipeline, rendering, diffing, templating). |
packages/commentary |
Private review/comment anchoring and editor (ProseMirror/Milkdown) runtime utilities. |
Development
bun install
bun run dev
The playground opens at / with this README as its landing page. Component examples live under
/c/<component-id>; for example, /c/accordion opens the Accordion component page.
Useful root commands:
bun run build
bun run lint
bun run typecheck
bun run test
bun run test:browser
bun run validate
Useful package commands:
bun run --filter=@lostgradient/cinder build
bun run --filter=@lostgradient/cinder components:check
bun run --filter=@lostgradient/cinder components:generate
bun run --filter=@lostgradient/cinder exports:check
bun run --filter=@lostgradient/cinder validate:consumer
bun run --filter=@lostgradient/chat build
bun run --filter=@lostgradient/chat components:check
bun run --filter=@lostgradient/chat validate:consumer
When adding or changing core components, update files under packages/components/src/components/<component-id>/, then run:
bun run --filter=@lostgradient/cinder components:generate
That regenerates schemas, variables, examples, constraints, the manifest, generated README sections, package exports, and related artifacts.
For the Chat family, update packages/chat/src/lib/components/<component-id>/ and run the matching @lostgradient/chat components:generate command.
Release
@lostgradient/cinder and @lostgradient/chat publish to npm. The @cinder/* workspaces remain private implementation packages that are packed into Cinder's published artifact when needed.
Each public package owns its packed artifact. Consumer validation, release dry runs, and the Changesets publish path build and inspect both tarballs before publishing Cinder first and Chat second.
Before a release, run:
bun run --filter=@lostgradient/cinder validate:consumer
bun run --filter=@lostgradient/cinder package:weight:check -- --existing-tarball
bun run --filter=@lostgradient/chat validate:consumer
bun run --filter=@lostgradient/chat package:weight:check -- --existing-tarball
Repository
- Source: github.com/stevekinney/cinder
- Issues: github.com/stevekinney/cinder/issues