@kismet-ux/constellation
v1.11.1
Published
Kismet Constellation — a themeable React component library: design tokens, light/dark, and 60+ router-agnostic components.
Maintainers
Readme
Kismet Constellation
A themeable React component library. ~60 components, light/dark out of the box, and a design-token system where one file holds every color decision.
No router dependency, no bundled fonts, no registry, no brand baked in — it's meant to be copied into a project and made yours.
npm install
npm run dev # → http://localhost:5173The dev server opens on Foundations, a live showcase of the whole token system: color families, type scale, spacing, radius, elevation, and components in context. Every other component has its own preview page in the sidebar.
Append ?theme=light, ?theme=dark, or ?theme=system to pin the mode.
Two ways to use it
Copy the repo (what it's designed for) — you own the source, and rebranding means editing _palette.scss and _theme.scss as described below.
Install it as a dependency:
npm install @kismet-ux/constellationimport '@kismet-ux/constellation/styles';
import { Button } from '@kismet-ux/constellation/ui';You only pay for what you render. Each component ships its own stylesheet, imported by the component itself, so your bundler drops the CSS for everything you don't use — the same way it drops the JS. Importing …/styles gives you the tokens and reset (4.6 kB gzipped); each component adds its own on top.
Installed, the SCSS is already compiled, so the two-file seam isn't available to you — retheme by overriding the custom properties after importing the stylesheet. Every token in the next section is fair game:
@import '@kismet-ux/constellation/styles';
:root {
--primary-main: #7c3aed;
--primary-emphasis: #6d28d9;
--font-sans: 'Inter', ui-sans-serif, system-ui, sans-serif;
}
:root:not([data-theme='light']) { /* your dark overrides */ }Making it yours
1. Color
Everything routes through two files:
| File | Holds |
|---|---|
| src/styles/_palette.scss | The raw ramps — $sky-500, $slate-900, … |
| src/styles/_theme.scss | The mapping to CSS custom properties, light and dark |
No component names a color. Change the ramps and the entire library follows.
Seven theme families ship by default — primary (sky), secondary (violet), info, success (emerald), warning (amber), destructive (rose), slate — and every one exposes the same ten roles:
main · dark · emphasis · light · contrast
focus · focus-visible · hover · outlined-border · selectedThat uniformity is what lets any component accept a theme prop and behave predictably:
<Button theme={'success'}>Save</Button>
<Chip theme={'warning'}>Pending</Chip>
<StatCard theme={'info'} label={'Queries'} value={42} />Two ramp shapes, chosen on purpose:
- Alpha tints (25–400) for states that sit over an unknown surface — hover, selected, focus. They compose correctly on light and dark without knowing what's behind them.
- Solid shades (500–950) for text and fills, where the value must be exact.
$info-* is the per-channel midpoint of $slate-* and $sky-*, shade for shade. Same hue as primary at lower chroma — which is what makes "info" read as the quiet informational tone rather than a competing accent. If you re-pick the primary, regenerate info the same way.
2. Type
--font-sans, --font-serif and --font-mono resolve to system stacks. Nothing to download, nothing to license, no flash of unstyled text. To brand it, override one variable:
:root { --font-sans: 'Inter', ui-sans-serif, system-ui, sans-serif; }src/styles/_fonts.scss has a font-face mixin and notes for self-hosting.
3. Brand
<Logo /> is a geometric mark plus a text wordmark — no baked-in SVG paths:
<Logo name={'Acme'} /> {/* rename */}
<Logo name={'Acme'} mark={<YourMark />} /> {/* or replace the glyph */}
<Logo name={null} /> {/* mark only */}Colors come from --logo-mark and --logo-fill. Inside a collapsed sidebar the wordmark folds away and the mark holds its place.
4. Space, radius, elevation
--size-1…--size-12, where--size-Nis N ÷ 2 rem. No magic numbers.--radius-xs|sm|md|lg|xl|2xl|full.- 24 elevation levels. In light mode height is carried by a two-layer key + ambient shadow; in dark mode, where shadow reads as nothing, by a progressively lighter surface. Both ramps are generated in
_theme.scss— change the formula once, not 24 times.
Routing
The library never imports a router. Out of the box Link renders a plain <a> and navigation is a full page load — correct everywhere, just not SPA-fast.
Wrap the app once and every link, breadcrumb, sidebar item and menu action upgrades together:
import { RouterProvider } from '@kismet-ux/constellation/navigation';
import { Link, useNavigate } from 'react-router-dom';
<RouterProvider linkComponent={Link} navigate={useNavigate()}>
<App />
</RouterProvider>Works the same with Next, TanStack Router, or anything else that exposes a link component and an imperative navigate.
Components take data, never globals: AppShell takes defaultSidebarOpen, NavUser takes user plus onSettings / onLogout.
Layout of the repo
src/
styles/ ← _palette.scss + _theme.scss: the theming seam
ui/ Button, Dialog, Table, Chip, Alert, StatCard, …
form/ Input, Select, Checkbox, DatePicker, Label
layout/ AppShell, Sidebar, Container, Row, Column, Grid
navigation/ Link, RouterProvider, Breadcrumbs, DropdownMenu, Sheet
typography/ Typography, Heading, TruncatedText
settings/ appearance switchers
user/ UserInfo, UserMenuContent
brand/ Logo
hooks/ useAppearance, useBreakpoint, useAlerts, useTableSorting, …
dev/ the preview harness you see at localhost:5173Each component is a folder: component-name.tsx + component-name.module.scss + index.ts.
Developing it against a real app
Publishing a version for every small change is how versioning goes off the rails. Instead, point a consuming app at a sibling checkout:
projects/
kismet-constellation/ ← this repo
your-app/In the app's vite.config.js:
import constellationLocal from '@kismet-ux/constellation/vite';
export default defineConfig({
plugins: [constellationLocal({ root: '../kismet-constellation' }), react()],
});…and in its .env.local:
CONSTELLATION_LOCAL=trueThe plugin builds resolve aliases from this package's own exports map, so there is no alias list to maintain. It aliases to dist, so keep a build running:
npx rollup -c --watch__CONSTELLATION_LOCAL__ is defined as true while it is on, which is what DevIndicator reads to show its badge. With the variable unset the plugin is inert and the app uses the published package, so nothing depends on the checkout existing.
⚠️ The plugin appends to server.fs.allow, and must. Setting that option at all replaces Vite's default, so listing only the library root locks the consuming app out of its own source — every module 403s and the page renders blank with no useful error.
Commands
npm run dev # dev harness
npm run build # bundle + type declarations → dist/
npm run typecheck # tsc --noEmit
npm run lint # eslint --fix
npm run stylelint # alphabetical SCSS property order
npm run format # prettierNotes for agents
AGENTS.md documents the architecture and conventions; .claude/skills/constellation-* carry per-category component references. Load the relevant skill before editing in a category.
