Follow

kickstartDS

An open-source design-system starter and UI toolkit — the framework behind everything else on this page

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.


FactDetail
Stars99 ★ on the monorepo
ScopeComponents, tokens, schemas, Storybook, docs
StackMDX · React · Storybook · HTML/CSS/JS
Co-foundedApril 2021

What's in it

A system, not a kit

Components plus the token layers, schemas and CMS wiring that turn a kit into a system teams can extend.

Schema-first

Component schemas generate types, CMS configuration and documentation — see the schema tooling.

Design decisions stay with the brand

Tokens and themes are the interface; components are the stable part.

In detail

What is actually inside it

Tokens as a stack

Branding → semantic → component. A theme overrides the layers it means to; components only ever read the layer above them.

Schemas are the contract

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.

Core web technologies, on purpose

HTML, CSS and JavaScript underneath, React on top: the design system stays useful to projects that are not React apps.

DetailValue
Structuremonorepo: styles/core, components, schema tooling, tooling
Token layersbranding → semantic → component
Stars99 ★ on the monorepo

Technical notes

The decisions behind it

What makes it a system rather than a kit?
The layers around the components: tokens that can be themed, schemas that can be validated, Storybook stories and documentation that can be generated, and a CLI that ties the build steps together. The components are the visible part.
Why open source it at all?
Because the expensive part was figuring out the shape — layered tokens, schema-first components, CMS generation — and none of that is a competitive advantage. The product is the design system a team builds with it.

Under the hood

One schema in, five artifacts out

29
Component schemas
Hand-written JSON Schema files across the three open modules — 17 in base, 5 in blog, 7 in form — each addressed by an http://schema.kickstartds.com/… $id that other schemas $ref across package boundaries.
12
Token templates
Style Dictionary generators in @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.
24
Component token modules
*-vars.scss files holding !default maps that compile into --c-* / --l-* custom properties. _button-vars.scss alone declares nine variants of .c-button.
2
Files for the whole system
@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

The documented path is five steps — start, design application, Storybook, components, publish — and the 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.
CommandWhat it does
kickstartds tokens initScaffolds a Style Dictionary setup from a `branding-token.json` — the same reduced input kickstartDS generates its own theme from.
kickstartds tokens compileCompiles the Design Token set; zero-config when the recommended project structure is used.
kickstartds schema dereferenceResolves every `$ref` into standalone schemas — the form a validator or a CMS importer can consume.
kickstartds schema typesGenerates TypeScript props for your own components from their schemas.
kickstartds completion installShell 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?
Token output depends on expressions like 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?
The four modules in the monorepo are dual-licensed MIT OR Apache-2.0, and the CLI, the schema tooling and the Storybook addons each live in their own repository. The fifth component module, @kickstartds/content, is commercial — the docs are explicit that it is an option, not a requirement.