Usage — CSS · SCSS · Tailwind
How to actually use this: a complete first page, the token contract, and how it composes with Tailwind, SCSS, your own CSS and any framework — plus what to check when it looks wrong.
Getting it
Two ways in today, and a third once the package is published. They give you the same CSS.
<link rel="stylesheet"
href="https://cdn.jsdelivr.net/gh/imswarnil/Creator-Design-System@main/dist/creator.min.css">the CDN — no install, no build, nothing to configure. Pin @main to a tag for production
Or download dist/creator.css from the repository and link it. That is the whole installation — no init step, no config file, no runtime.
npm install creator-design-systemcoming with the first tagged release; the examples below use these paths
The npm package is not published yet, so that command does not work today. Everything below is written against the package paths it will use, because they are the paths the exports map in package.json already declares — the CDN link above gives you the same stylesheet in the meantime.
Your first page
A complete document — nothing omitted. Copy it into an .html file, open it, and the system is running.
<!doctype html>
<html lang="en" data-theme="light">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>My site</title>
<!-- 1 - the type the system expects -->
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=Inter:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500&display=swap">
<!-- 2 - the system -->
<link rel="stylesheet" href="node_modules/creator-design-system/dist/creator.css">
<!-- 3 - your overrides, always AFTER the system -->
<style>
:root {
--accent: #f04e2e;
--font-display: 'Space Grotesk', sans-serif;
}
</style>
</head>
<body>
<header class="nav-shell">
<nav class="nav-bar" aria-label="Main">
<a class="logo logo-sm" href="/">Your name</a>
<div class="nav-links">
<a class="nav-link" href="/watch/" aria-current="page">Watch</a>
<a class="nav-link" href="/learn/">Learn</a>
</div>
<div class="nav-actions">
<a class="btn btn-primary btn-sm btn-pill" href="/subscribe/">Subscribe</a>
</div>
</nav>
</header>
<main class="container section">
<h1 class="t-display-2">A page, already styled</h1>
<p class="t-lead u-mt-5">Every class above exists. Nothing was invented.</p>
<div class="deck deck-sm u-mt-8">
<article class="card">
<div class="card__body">
<p class="card__meta">Build log - 12 min</p>
<h2 class="card__title"><a class="card__link" href="/post/">A post</a></h2>
<p class="card__excerpt">The card is the same object in every collection.</p>
</div>
</article>
</div>
</main>
</body>
</html>no build step, no bundler, no framework: this is the whole integration
The fonts are the only external dependency, and even they are a choice. Point --font-display, --font-body and --font-slate at anything you like and drop the <link>.
Taking less than all of it
Import a layer directly when you do not want the whole system. Each layer needs only the ones below it, so you can stop at any floor.
/* everything, in one line */
@import "creator-design-system";
/* or floor by floor - later layers need the earlier ones */
@import "creator-design-system/foundation"; /* tokens only */
@import "creator-design-system/elements"; /* + text, badge, table, code */
@import "creator-design-system/components"; /* + buttons ... navbar */
@import "creator-design-system/sections"; /* + hero, stats, CTA, footer */
@import "creator-design-system/utilities"; /* + u- helpers */
/* not included by default: it ships to YouTube and Instagram, not a website */
@import "creator-design-system/broadcast";foundation alone is already usable — it is where every decision lives
Customising — the token contract
Never edit the source. Override tokens after the import; every component reads them live, in both themes. This is the entire customisation API.
@import "creator-design-system";
:root {
--accent: #6d4aff; /* your signal colour */
--font-display: 'Clash Display', sans-serif;
--radius-card: 1.25rem;
}three lines rebrands the site, the buttons, the cards and the thumbnails
Tokens come in two tiers, and that distinction is the whole reason one override reaches everything. Primitives are the ladders — --ink-500, --signal-500, --space-4. Semantic tokens name a job — --fg-muted, --accent, --line-default — and point at a primitive. Components read the semantic tier only, so re-pointing one semantic token changes every component that meant that thing.
| Token | What it decides |
|---|---|
--accent | the one rationed colour: buttons, active states, the record light |
--bg-canvas · --bg-surface · --bg-sunken | the three background depths |
--fg-default · --fg-muted · --fg-subtle · --fg-faint | the text ladder, strongest first |
--line-subtle · --line-default · --line-strong | the three hairline weights |
--font-display · --font-body · --font-slate | headings · reading · labels and code |
--space-1 … --space-16 | the spacing ladder — nothing invents a gap outside it |
--radius-sm … --radius-pill | the corner ladder, plus --radius-card |
--dur-1 … --dur-5 · --ease-out | motion: durations and curves |
The full list lives on Color, Typography and Spacing — or in one read at llms-full.txt.
Light and dark
Both themes come from the same variables. Nothing is duplicated, and no component knows which theme it is in.
<html data-theme="dark">the switch — that is all the system needs
// Follow the OS, remember a choice, and set it before first paint so the page
// never flashes the wrong theme.
(function () {
var saved = localStorage.getItem('theme');
var dark = saved ? saved === 'dark'
: matchMedia('(prefers-color-scheme: dark)').matches;
document.documentElement.dataset.theme = dark ? 'dark' : 'light';
})();inline in <head>, above the stylesheet link
With Tailwind
The two do different jobs and compose well: Tailwind gives you utilities, this gives you decided components. The u- prefix means there are no class collisions.
Order matters more than anything else here. Both ship a reset — Tailwind calls its one Preflight — and the last one loaded wins. Import Tailwind first, so this system’s reset and components sit on top of it.
/* app.css - Tailwind 4 */
@import "tailwindcss";
@import "creator-design-system"; /* after Tailwind, always */
/* Hand the tokens to Tailwind so bg-accent, text-muted and friends exist.
`inline` is the important word: it tells Tailwind these values reference
other custom properties, which keeps modifiers like bg-accent/50 working. */
@theme inline {
--color-accent: var(--accent);
--color-canvas: var(--bg-canvas);
--color-surface: var(--bg-surface);
--color-ink: var(--fg-default);
--color-muted: var(--fg-muted);
--radius-card: var(--radius-card);
}Tailwind 4 — one import each, then map the tokens
// tailwind.config.js - Tailwind 3
module.exports = {
theme: {
extend: {
colors: {
accent: 'var(--accent)',
canvas: 'var(--bg-canvas)',
surface: 'var(--bg-surface)',
ink: 'var(--fg-default)',
muted: 'var(--fg-muted)',
},
borderRadius: { card: 'var(--radius-card)' },
},
},
// Optional: this system brings its own reset, so you may not want two.
corePlugins: { preflight: false },
};Tailwind 3 — the same idea, in the config file
Now both vocabularies work on one element, and they agree by construction, because they are reading the same variable:
<article class="card mt-8 lg:mt-12">
<div class="card__body">
<h2 class="card__title">Component from the system</h2>
<p class="text-muted">Spacing and layout from Tailwind</p>
</div>
</article>
<!-- change --accent once and both of these follow -->
<button class="btn btn-primary">System button</button>
<span class="bg-accent text-white px-3 py-1 rounded-full">Tailwind chip</span>no collisions — every helper here is u- prefixed and every component is a noun
| Which one | For what |
|---|---|
Use the system for | components with parts and states — cards, navbar, forms, buttons, sections. The things where the decisions matter. |
Use Tailwind for | one-off layout on a page: grid, flex, margins, responsive tweaks. |
Do not | rebuild a card out of utilities. That is how two pages stop matching. |
If you would rather not load two resets at all: import creator-design-system/foundation and skip 00-reset.css, or turn Preflight off as above. Either is fine. Loading both in the wrong order is not.
With SCSS
Nothing special is required, and one thing is worth knowing.
// main.scss
@use "creator-design-system/dist/creator.css";
.promo {
// SCSS variables are compile-time; the tokens are not, and that is the point
padding: var(--space-6);
border-radius: var(--radius-card);
background: var(--bg-sunken);
}the tokens stay custom properties, so runtime theming survives compilation
Resist copying tokens into SCSS variables. A SCSS variable is resolved when you build; a custom property is resolved when the page runs. Only the second can flip to dark mode, or be re-pointed by a reader setting, without a rebuild.
With your own CSS
The system is meant to be built on. There is a right way to add to it, and it is the same way the system adds to itself.
@import "creator-design-system";
/* Your component. Written in the system's tokens, so it inherits the theme,
dark mode, the motion rules and any future rebrand - for free, forever. */
.testimonial {
display: grid;
gap: var(--space-4);
padding: var(--space-6);
background: var(--bg-surface);
border: var(--border-hair) solid var(--line-default);
border-radius: var(--radius-card);
box-shadow: var(--shadow-1);
}
.testimonial__quote {
font-family: var(--font-display);
font-size: var(--text-xl);
line-height: var(--leading-snug);
color: var(--fg-default);
text-wrap: balance;
}
.testimonial__who {
font-family: var(--font-slate);
font-size: var(--text-2xs);
letter-spacing: var(--tracking-slate);
text-transform: uppercase;
color: var(--fg-faint);
}
/* State goes in ARIA, not in a class the accessibility tree cannot see. */
.testimonial[aria-current] { border-color: var(--accent); }a new component that behaves like a native one, because it reads the same variables
| Extending the system | |
|---|---|
Do | write new components in tokens — var(--space-4), never 16px |
Do | follow the naming: .block, .block__part, .block-variant |
Do | style from ARIA — [aria-current], [aria-expanded], [data-*] |
Don’t | edit anything inside src/ — the next update overwrites it |
Don’t | add a second accent hue; the palette is closed so one colour can mean something |
Don’t | invent an .active class. It can disagree with the accessibility tree, and then the two of them tell different people different things |
In a framework
It is a stylesheet, so it goes where stylesheets go.
| Stack | How |
|---|---|
React · Next · Vue | import the CSS once at the app root, then use className="card" as normal. No provider, no theme object, no runtime |
Astro · 11ty · Hugo | link it in the base layout |
Ghost | link it in default.hbs — the collection cards were designed against routes.yaml |
Svelte | import in the root component, or link it in app.html |
The two optional scripts — nav.js and highlight.js — are plain, framework-free and additive. Without them you get sticky bars, click-only dropdowns and uncoloured code. Nothing breaks.
Reading the class names
The shape of a name tells you what kind of thing it is, so you can guess correctly.
| Shape | Means |
|---|---|
.card | a component — a noun, on its own |
.card__title | a part of it — two underscores |
.card-inverse | a variant of it — one hyphen |
.btn-primary · .btn-sm | intent and size on the same component |
.u-mt-6 · .u-fg-subtle | a utility — always u- prefixed, always one job |
.t-display-1 · .t-lead | a type role, not a size |
[aria-current] · [aria-expanded] | state — an attribute, never a class |
When it looks wrong
Five things account for nearly all of it.
| Symptom | Almost always |
|---|---|
Everything is unstyled | the class does not exist. Check it against llms-full.txt — nothing in this system is invented, so a name either is or is not real |
Overrides do nothing | they are above the import. Tokens must be set after the system loads |
The type looks wrong | the fonts are not loaded. Add the <link>, or re-point --font-* at what you do have |
Tailwind flattened it | Preflight loaded last. Import Tailwind first, or turn Preflight off |
Dark mode does nothing | data-theme is missing from <html> — it does not go on <body> |