kickstartDS is a design-system starter and UI toolkit: a component library, a token system, schemas and a Storybook workflow, so a team can build its own brand-compliant design system instead of starting from an empty repository.
It stays on core web technologies — HTML, CSS, JavaScript — with first-class React templating, and it is the framework my day job and this site are both built on.
| Fact | Detail |
|---|---|
| Stars | 99 ★ on the monorepo |
| Scope | Components, tokens, schemas, Storybook, docs |
| Stack | MDX · React · Storybook · HTML/CSS/JS |
| Co-founded | April 2021 |
What's in it
Components plus the token layers, schemas and CMS wiring that turn a kit into a system teams can extend.
Component schemas generate types, CMS configuration and documentation — see the schema tooling.
Tokens and themes are the interface; components are the stable part.
In detail
What is actually inside it
Branding → semantic → component. A theme overrides the layers it means to; components only ever read the layer above them.
Every component describes itself with JSON Schema, and the types, CMS configuration and documentation are generated from that — so a prop change cannot drift three ways.
HTML, CSS and JavaScript underneath, React on top: the design system stays useful to projects that are not React apps.
| Detail | Value |
|---|---|
| Structure | monorepo: styles/core, components, schema tooling, tooling |
| Token layers | branding → semantic → component |
| Stars | 99 ★ on the monorepo |
Technical notes
The decisions behind it
What makes it a system rather than a kit?
Why open source it at all?
Under the hood
One schema in, five artifacts out
base, 5 in blog, 7 in form — each addressed by an http://schema.kickstartds.com/… $id that other schemas $ref across package boundaries.@kickstartds/style-dictionary, each with its own JSON Schema. The branding input is validated with Ajv before generation, and every generated token set is validated again afterwards — invalid output throws instead of shipping.*-vars.scss files holding !default maps that compile into --c-* / --l-* custom properties. _button-vars.scss alone declares nine variants of .c-button.@kickstartds/core's bundle script walks every @kickstartds/*/lib/exports.json, topologically sorts the package graph, and emits one minified ESM index.js plus one compressed index.css.kdsBundle, the binary shipped by @kickstartds/bundler, is where the pipeline lives. yarn schema at the repo root is lerna run --parallel schema, which runs kdsBundle --schema in every package: for each source/**/!(_)*.(schema|definitions|interface).json it writes a raw copy, a dereferenced <name>.dereffed.json, a <Name>Props.ts for the types, a <name>.stories.ts, and — once the stylesheet has been compiled — a <name>-tokens.json. A plain kdsBundle run adds the bundling step; --watch puts the schema transform and the bundler under chokidar.
The generated story carries jsonschema: { schema } and ...getArgsShared(schema), so Storybook's Controls come from the same schema that produces the types. The component-token panel is fed from the fifth artifact: custom-property-extract reads the compiled CSS and writes <name>-tokens.json, which createStory imports only when the file exists. Token documentation therefore cannot drift from the stylesheet it documents.
Two decisions that shape everything
Where the flexibility actually comes from
Colours must come in pairs. primitives.schema.json requires eight colour keys — primary/primary-inverted, background/background-inverted, foreground/foreground-inverted, link/link-inverted — and Ajv rejects a branding file that is missing one. An additional colour only gets a full scale if its -inverted counterpart is present.
From each pair the generator emits four ladders: the base value, nine alpha steps, nine mixes toward the page background (--ks-color-primary-to-bg-1 … -9) and nine toward the foreground. Those nine steps are not linear — they are sampled from a Bézier curve (bezier-js, control points (0,0) (1,0.1) (0,0.9) (1,1)), so tints close to the base are spaced more tightly than the distant ones.
The payoff shows up in markup: any element can carry ks-inverted="true", and the generated stylesheet contains a matching block that re-points --ks-color-fg and --ks-color-bg at the -inverted bases. A dark surface is the same variables re-declared, not a second palette — which is how the bold Links section further down this page works. The selectors are written as :root, [ks-theme], so any element with a ks-theme attribute receives a complete, independent set of custom properties: one page, several themes.
Components read the layer above them. A component's SCSS never names a brand colour. It declares a map of its own variables and includes a single mixin — $vars: meta.module-variables(button-vars); @include custom-properties.styles($vars); — which turns every map entry into a --c-button--* custom property (layout components use the l- prefix). The rest of button.scss only ever reads those variables. Because the maps are !default, a consuming project can reassign component tokens before the component loads, without forking it.
Layout asks its container, not the viewport. The responsive rules in component styles are container queries, not media queries: section.scss calls container.wrapper('section') and then reacts through container.size('≤', 776px). The token layer is the deliberate exception — per-breakpoint font-size and spacing factors are emitted as @media rules. With the default branding tokens (spacing.base: 8, scale-ratio: 1.618) the spacing scale spans 0.118rem to 2.118rem, and those values are multiplied by 1.414 / 2 / 2.828 / 4 from the four breakpoints upwards.
Adopting it
What a consuming project actually runs
kickstartDS/ds-guide repository keeps one branch per step (step/1 … step/4), so you can diff the result of each one. The kickstartds CLI is meant to be pinned as a devDependency of your own design system rather than installed globally, and it reads its options from .rc files that are themselves described by JSON Schema — a missing value falls back to a default or an interactive prompt instead of failing.| Command | What it does |
|---|---|
| kickstartds tokens init | Scaffolds a Style Dictionary setup from a `branding-token.json` — the same reduced input kickstartDS generates its own theme from. |
| kickstartds tokens compile | Compiles the Design Token set; zero-config when the recommended project structure is used. |
| kickstartds schema dereference | Resolves every `$ref` into standalone schemas — the form a validator or a CMS importer can consume. |
| kickstartds schema types | Generates TypeScript props for your own components from their schemas. |
| kickstartds completion install | Shell completions, built on `omelette`; the docs describe this one as early and not widely tested. |
Rough edges
What the repository says about itself
What breaks when you edit a schema in watch mode?
--watch re-dereferences only the schema that changed. There is a standing TODO in the bundler's schema.ts: editing button.schema.json does not regenerate teaser-box.schema.dereffed.json, even though the latter $refs the former. A full yarn schema is the workaround.Why are two dependencies patched?
patch-package carries a fork of @bcherny/json-schema-ref-parser@10.0.5 and a fix for replace-in-file@7.1.0. The dereferencing pipeline depends on behaviour that is not in the upstream releases, so the fork is pinned in patches/ and re-applied on install.Why does the CSS minifier lose its calc optimisation?
calc(var(--ks-font-size-copy-m-base) * var(--ks-font-size-copy-m-bp-factor, 1)), so the bundler calls cssnano({ preset: ['default', { calc: false }] }). Minifying that away would break responsive type.Is all of it open source?
@kickstartds/content, is commercial — the docs are explicit that it is an option, not a requirement.Links
Where to find this one
