The Two Mindsets — Page-Builder vs Systems Thinker
Every designer on a project has one of two internal monologues when they start working:
"I need to make this heading dark blue, 32px, with 1.5rem margin below."
"This button should be navy with white text and 10px 24px padding."
"I'll style this card with a white background, 1px border, and 12px radius."
"This is a heading-section. Does a token for that exist already?"
"This is a primary button. Apply the button token + variant."
"This is a Card component. It should use the background-card variable."
The page-builder produces a site that looks right today. The systems thinker produces a site that stays right forever — no matter how many pages are added, no matter which team member works on it, and critically: no matter how many different clients use the same underlying system.
A school does not describe each student's uniform individually: "Sipho wears a white shirt, grey trousers, and black shoes." Instead they define a single rule — "the school uniform" — and every student follows it. Change the rule once and 800 students update simultaneously. That rule is a design token. Sipho's specific shirt is a local style.
Why manual styling feels faster — and why it isn't
When you are new, manual styling feels faster because the feedback is immediate. You click, you see a result, it looks right. There is no planning overhead. But you are spending time instead of saving it. Every pixel you type by hand is a debt that must be repaid — when the client changes their brand, when you onboard a new team member, when you add 20 more pages.
Systems thinking has a setup cost and an exponential return. The graph inverts around week 3 of a project, and by month 2 you are building pages in a fraction of the time a manual-styler would need.
What Craft Actually Is — and Why It Exists
Craft is not a style library. It is a set of conventions, CSS variables, and token standards that make Webstudio projects fast, maintainable, and shareable — regardless of what brand or client is using them.
That last part is crucial. Craft does not tell you what colours to use. It tells you where to put your colours, and what to name them, so that any section template built to Craft standards can be dropped into any client project and automatically inherit that client's brand.
"Templates contain no hardcoded values such as colors or sizes. They rely entirely on Craft CSS variables, so each section adapts to your site's design system."
Craft in 3 sentences
- CSS variables define your site's values — colours, spacing, sizing — in one place called Global Root.
- Tokens are named style bundles applied to elements. They reference those variables, not hardcoded values.
- Templates from the Craft Library use only those variables, so they adapt to any brand automatically.
How to get started with Craft
- Go to Marketplace → Pages → Craft and insert the Style Guide page.
- Open Global Root and customise the CSS variable values to match your client's brand.
- Map the semantic variables (
--foreground-primary,--background-primary, etc.) to your brand colours. - Start building using those predefined variables — never by hardcoding values.
Craft's Style Guide page shows you every HTML element and component styled using the variables. When you customise the variables, the entire style guide updates. This is the system working exactly as designed.
Craft's page structure standard
Every Craft project follows this navigator structure:
/* Craft standard page structure (from the docs) */ Page Wrapper Slot Global Styles /* CSS variables live here */ Nav Main Section Hero /* prefix sections with "Section " */ Container /* uses the "container" token */ Section Services Container Slot Footer
Craft is a building blueprint standard. Architects everywhere use the same conventions for what a "wall" symbol means, what "door" symbols mean, what scale is used. An architect from Durban can read a blueprint from Cape Town without any briefing. Craft does the same for Webstudio projects — any trained designer can open any Craft project and understand it immediately.
The Craft Variable System — All of Them, Explained
Craft defines a specific set of CSS variables. These are the only variables your tokens should reference for colour and spacing. You set their values in Global Root once per project. Here they all are:
Foreground variables — text, icons, borders
Background variables — surfaces, cards, pages
Spacing variables — gaps and rhythm
Interaction variables
Never hardcode a colour value directly in a token. Write color: var(--foreground-primary) — not color: #050a1e. The moment you hardcode a value, you break the system and create manual maintenance work.
Foreground vs Background Tokens — The Most Important Distinction
This is where most beginners make their first big mistake. They pick a colour because it looks right — without asking what role that colour is playing.
Foreground = what sits on top. Text, icons, borders, outlines — anything drawn over a surface.
Background = the surface itself. The page, a card, a section, a button face.
The cup is background. The coffee inside is background. The label printed on the cup is foreground. The handle is structure. You would never put "label text" on the background token — even if today it happens to be the same colour as the label. Their roles are different, and roles can change independently.
Why this matters — the dark mode proof
Craft's system lets you flip an entire site from light to dark mode by changing ~6 variable values. This only works if every element is using the right semantic token for its role.
/* Light mode — set in Global Root */ --foreground-primary: #050a1e; /* dark text */ --background-primary: #f4f3f0; /* light page */ --background-card: #ffffff; /* white card */ /* Dark mode — change only these values */ --foreground-primary: #f4f3f0; /* light text ← flipped */ --background-primary: #050a1e; /* dark page ← flipped */ --background-card: #0a1540; /* dark card ← flipped */ /* Every element that used var(--foreground-primary) updates. */ /* Every element that used var(--background-card) updates. */ /* Elements with hardcoded colours: nothing. They stay wrong. */
Common mistakes
Using --background-accent for text colour because it happens to be the right shade today.
Using --foreground-primary as a button background because it's dark and looks good.
Text always uses a --foreground-* variable. Full stop.
Surfaces and backgrounds always use a --background-* variable. Full stop.
The practical rule
Before choosing any token for a colour property, ask one question: "Is this element drawn ON TOP of something, or IS it the surface?" If on top → foreground token. If it is the surface → background token.
| CSS Property | Token Family | Why |
|---|---|---|
| color | --foreground-* | Text sits on top of a surface |
| background-color | --background-* | This property defines a surface |
| border-color | --foreground-border | Borders are drawn on top of/around a surface |
| fill (SVG icons) | --foreground-* | Icons sit on top of surfaces |
| box-shadow color | --foreground-* | Shadows are rendered over the page surface |
- Look at any webpage. Pick 10 elements at random.
- For each one, decide: is it a surface or is it drawn on top of something?
- Assign each to a Craft token:
--background-card,--foreground-primary, etc. - Notice how every single colour choice fits into one of these 9 variables. Nothing needs to be outside the system.
Utility Tokens vs Semantic Tokens — and How Craft Names Them
Craft distinguishes between two types of tokens. This is official Craft doctrine — not opinion.
| Type | Casing | Examples | Purpose |
|---|---|---|---|
| Utility | kebab-case | margin-auto container button | Layout and styling without conveying meaning about content |
| Semantic | Title Case | Card Pricing Table Team Member | Conveys meaning and context about what the element IS |
| Variant | kebab-case | is-button-secondary is-pricing-table-small | A modifier used WITH a base token — never alone |
| Size | kebab-case | button-small button-large | T-shirt sizing appended to a base token |
The naming rule you need to memorise
Utility tokens describe what they do. container creates a max-width container. margin-auto centres an element. The name is the instruction.
Semantic tokens describe what something IS. Card means this element IS a card — a content container with a background, padding, and border. The name is the identity.
dark-blue-box — what happens when the client changes to a light theme?
big-red-button — what happens when the button changes size?
white-rounded-card — what happens in dark mode?
Card — always correct, regardless of colour or size
button + is-button-primary — always describes role
Section Hero — always describes position and purpose
"The variant should never be used without the base. is-button-secondary uses some of the base styles and modifies or adds others." — This is the stacking pattern. Always apply base token first, then variant.
- Look at the tokens in any Webstudio project you have worked on.
- Identify any tokens named by appearance (colour, size, position).
- Rename each one to describe its role or purpose instead.
- For each semantic token, ask: "If I change the colour tomorrow, does this name still make sense?" If no → rename it.
When to Use a Token vs a Local Style — The Craft Decision Tree
One of the most common questions is: "Should I make this a token or just style it locally?" Craft's documentation gives a clear answer.
There are multiple styles and at least one has many value options (like colour or size). OR when there is a common use case that takes more time to configure manually than applying an existing token — such as a flex layout with gap.
You can click a single button in the Style Panel to apply it (like text-align: center). OR when there is only one style property involved — use a CSS variable directly instead.
The practical questions to ask
- Will I use this combination of styles more than once? If yes → token.
- Is this a single property? If yes → CSS variable directly, or Local.
- Is this a simple toggle like text-align or display flex? Use the Style Panel button, not a token.
- Would a new team member need to understand this to work on the project? If yes → it needs a name → token.
A recipe book lists a "caramel sauce" recipe once. Every dessert that needs it says "add caramel sauce — see p.47." If you wrote out the full caramel sauce method inside every dessert recipe, and the chef wanted to change the sugar ratio, they would need to update 8 recipes. DRY principle: one source of truth. One update. Everything stays consistent.
px vs rem vs em vs % — What Each One Does and When to Use It
This is one of the most misunderstood areas of CSS. Most beginners use px for everything because it's predictable. But this breaks accessibility, creates rigid layouts, and makes responsive systems harder than they need to be.
px — absolute, rigid, ignores user preferences
px is a fixed pixel value. 16px is always 16px, regardless of the browser font size setting, the viewport, or the parent element. It does not scale. It does not respond to accessibility settings where a user has set their browser default font to larger.
rem — root-relative, accessible, preferred for font sizes and spacing
rem stands for "root em." It is always relative to the html element's font size (16px by default). If a user has set their browser to a larger default font (for accessibility), your rem values scale automatically. This is why Craft uses rem for all its spacing variables.
/* Use rem for: */ font-size: 1rem; /* 16px — scales with user preferences */ padding: 1.5rem; /* 24px — consistent, accessible */ margin: 2rem; /* 32px */ gap: var(--gap-m); /* Craft handles the rem value for you */ /* Open Props size scale uses rem: */ --size-3: 1rem; /* 16px */ --size-5: 1.5rem; /* 24px */ --size-7: 2rem; /* 32px */
em — parent-relative, useful for component-internal spacing
em is relative to the current element's font size (or its closest parent that sets a font size). This makes it useful when you want spacing to scale proportionally with the text inside a component — for example, a button's padding.
/* em is good for: internal component scaling */ .button { font-size: 1rem; padding: 0.6em 1.5em; /* scales with the button's own font-size */ } .button-large { font-size: 1.25rem; /* padding automatically scales — no override needed */ }
% — relative to parent, essential for fluid layouts
% is relative to the parent element for widths, and to the element itself for padding-top/bottom percentage values. It is the foundation of fluid, responsive layouts.
/* % for fluid widths */ width: 100%; /* fill the parent — the basis of all responsive grids */ max-width: 50%; /* maximum half the parent */ /* % for aspect ratios (the padding-top trick) */ padding-top: 56.25%; /* = 16:9 ratio (9/16 = 0.5625) */
The decision rule
| Use case | Unit | Why |
|---|---|---|
| Font sizes | rem | Accessibility — scales with user preferences |
| Vertical spacing (margin, padding) | rem | Consistent rhythm, accessibility-aware |
| Button internal padding | em | Scales proportionally with the button's font |
| Layout widths | % or fr | Fluid, responsive |
| Container max-width | px or ch | Hard limit — doesn't need to scale |
| Border width | px | Should be visually precise, not scale |
| Box-shadow blur/spread | px | Visual decoration — fixed is fine |
- Open any project you've built. Look at 10 font-size values. How many use
px? - Convert each
pxfont size toremby dividing by 16. (24px→1.5rem) - Look at spacing (padding, margin, gap). How many use
pxvsrem? - Check if any widths are fixed
pxwhere they should be%orfr. - Rebuild one component using only rem for typography and spacing, % or fr for widths, and px only for borders and shadows.
calc() and clamp() — Doing Maths in CSS
Once you understand units, calc() and clamp() unlock a new level of responsive design — sizes that respond to the viewport without needing breakpoints.
calc() — combine units and do arithmetic
calc() lets you mix different unit types in a single expression. This is impossible otherwise — you cannot write 100% - 2rem in plain CSS without it.
/* Typical uses of calc() */ width: calc(100% - 2rem); /* full width minus 1rem margin each side */ height: calc(100vh - 75px); /* full viewport minus nav height */ padding: calc(var(--gap-m) * 1.5); /* 1.5× a Craft variable */ margin-top: calc(var(--size-8) + 2rem); /* Craft uses calc() in its fluid spacing variables: */ --gap-m: var(--size-fluid-4); /* which is: */ --size-fluid-4: clamp(2rem, 4vw, 3rem);
clamp() — fluid values between a minimum and maximum
clamp(min, ideal, max) sets a value that scales fluidly between two limits. The ideal value (usually a vw unit) grows with the viewport, but is always constrained between min and max.
/* clamp(minimum, ideal-fluid, maximum) */ font-size: clamp(1rem, 2.5vw, 1.5rem); /* ↑ never smaller ↑ fluid ↑ never larger */ /* Open Props uses clamp() throughout — this is from Craft's variable set: */ --font-size-fluid-1: clamp(1rem, 4vw, 1.5rem); --font-size-fluid-2: clamp(1.5rem, 6vw, 2.5rem); --size-fluid-3: clamp(1.5rem, 3vw, 2rem); /* container max-width (from Craft): */ --container-max-fluid: clamp(285px, 90%, 1350px); /* never narrower than 285px, never wider than 1350px, 90% in between */
A heading with font-size: clamp(1.5rem, 5vw, 3rem) scales smoothly from mobile to desktop. You do not need a breakpoint to make it smaller on mobile. This is why Craft's fluid variables eliminate the need for most manual breakpoint overrides.
max-width, min-height, max-height — Constraining Without Breaking
Setting a fixed width or height on an element is one of the most common ways to create a layout that breaks on different screens. Constraints are almost always better than fixed dimensions.
max-width — the container pattern
max-width lets content be fluid up to a limit, then stops growing. This is the foundation of the Craft container token.
/* ❌ Fixed — breaks on small screens, looks odd on very wide screens */ width: 1200px; /* ✅ Fluid with constraint — the container pattern */ width: 100%; max-width: var(--container-max-fluid); /* clamp(285px, 90%, 1350px) */ margin: 0 auto; /* centre it */ /* Reading width constraint (line length comfort) */ max-width: 65ch; /* ch = width of "0" character — great for text */
min-height — for sections and hero areas
min-height ensures a section is at least a certain height, but can grow to fit content. Never use a fixed height on a section that contains text — it will overflow on small screens or cut off dynamic content.
/* ❌ Fixed height — text overflows when it doesn't fit */ height: 500px; /* ✅ Minimum height — grows with content */ min-height: 500px; /* or: */ min-height: 60vh; /* at least 60% of viewport height */ /* or: */ min-height: clamp(300px, 50vh, 700px); /* constrained fluid */
max-height with overflow — for scrollable areas
/* Popup or dropdown — constrained height with scroll */ max-height: 85svh; /* svh = small viewport height (mobile-safe) */ overflow-y: auto; /* Craft uses this pattern: */ --popup-max-height: 85svh;
width: 800px — broken on mobile
height: 400px — text overflows
font-size: 48px — too large on mobile
width: 100%; max-width: 800px
min-height: 400px — grows with content
font-size: clamp(1.5rem, 5vw, 3rem)
Responsive Systems — Designing for Every Screen from the Start
Responsive design is not about adding breakpoints at the end. It is about using the right units and constraints from the beginning so that most layouts need very few breakpoint overrides at all.
The responsive mindset shift
Build the desktop layout in px. Then add breakpoints to undo all the fixed values.
Result: double the work, fragile system, breakpoints fighting each other.
Use clamp(), %, fr, and rem from the start. The layout already works.
Result: minimal breakpoints, clean system, consistent at all sizes.
Webstudio breakpoints — how Craft handles them
Webstudio uses max-width breakpoints by default. When you select a breakpoint like 991px, the canvas shows at 768px — this is intentional. You are styling for the minimum width of that breakpoint range, to catch edge cases.
Style the minimum extreme of each breakpoint. When the viewport hits 767px, the next breakpoint triggers. Design there, not at the comfortable middle.
The fluid-first toolkit
/* These four patterns cover most responsive needs: */ /* 1. Fluid container */ width: min(90%, 1200px); margin: 0 auto; /* 2. Auto-fit grid — wraps automatically, no breakpoints needed */ display: grid; grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr)); /* 3. Fluid typography */ font-size: clamp(1rem, 2.5vw, 1.5rem); /* 4. Flexible padding */ padding: clamp(1rem, 5vw, 3rem);
- Build a 3-column card grid using
auto-fitandminmax. - Resize your browser from 1400px down to 320px without adding any breakpoints.
- The grid should reflow naturally — 3 columns, then 2, then 1. If it doesn't, the units are wrong.
- Add
clamp()to the card title font size. Observe it scaling as you resize. - Note: no breakpoint was added. The layout is responsive by nature of its units.
Two Clients, One System — Why Craft Works Across Any Brand
Here is the most important practical demonstration of everything you have learned. Look at these two client builds — completely different brands, different colours, different energy. Both built in Webstudio. Both using the exact same Craft token structure.
The token names are identical across both projects. --background-card on Client A resolves to white. --background-card on Client B resolves to deep navy. The card component's token says var(--background-card) in both projects — the Global Root value is what makes them look different.
"The same section template dropped into Client A becomes light and golden. Dropped into Client B becomes dark and red. Zero restyling. This is why Craft Library sections have no hardcoded values — they are designed to inherit whatever the client defines."
What you just saw — summarised
- One Craft section template = one set of token names
- Client A's Global Root = their brand values mapped to those token names
- Client B's Global Root = their brand values mapped to the same token names
- The template is brand-agnostic. The Global Root is where brand identity lives.
- This is how large agencies run 40+ projects simultaneously — one system, infinite skins.
- Build a Craft hero section using only Craft variables — no hardcoded colours.
- Set Global Root to Theme A: light background, yellow accent, dark text.
- Screenshot the result.
- Now change only the Global Root values to Theme B: dark background, red accent, light text.
- Screenshot the result.
- Compare the screenshots. Zero elements were individually restyled. This is the system working.
Alarm Bells — Recognise When to Stop and Apply Systems Thinking
Stop. Ask: "Is there a Craft variable for this role?" There almost certainly is. Use var(--foreground-primary) not #050a1e.
Stop. Convert to rem. Divide by 16. 24px → 1.5rem. Every font size should be rem unless you have a very specific reason.
Stop. Use width: 100%; max-width: [value]. Fixed widths break on smaller screens. Constraints are always better.
Stop. Use min-height. Sections must grow with their content. Fixed height causes overflow on every screen that doesn't match your design viewport.
Stop. If you are doing this, a token should exist. Give it a name, create the token, apply it everywhere. You are building debt, not a site.
Stop. Text always gets a foreground token. If you are using --background-accent on a color: property, you are using the wrong token family.
Stop. dark-blue-box will be wrong the moment the colour changes. Name it by role: Card, Section Hero, button.
Stop. Before building any section from scratch, check the Marketplace → Craft Library. There may already be a template for this pattern that uses Craft variables and Craft structure correctly.
Pre-Handoff QA Checklist — Run This on Every Project
--foreground-primary, --background-primary etc.) in Global Rootvar(--...)margin-auto, container, button)Card, Pricing Table)is- prefix and are never applied without their base tokenrem (not px) — accessibility-awarerem or Craft gap variableswidth on containers — using max-width with width: 100%height on content sections — using min-height insteadclamp() or Open Props fluid variablespx used only for borders, shadows, and truly fixed decorative valuesCards → Card)auto-fit / auto-fill with minmax where possible — minimal breakpointscan work on any client, any day.
The client's colours live in Global Root. The structure lives in Craft. The components live in tokens. Change the root, and the entire brand follows. This is the system.