Webstudio · Craft · Systems Design

Stop Building Pages.
Start Building Systems.

A complete training guide to Craft, design tokens, CSS units, responsive thinking, and maintainable frontend architecture — skills that work on every client, every project, every time.

Craft Framework Design Tokens Foreground vs Background rem / em / % / calc() Responsive Design max-width / min-height DRY Principle Maintainability
01
Mindset

The Two Mindsets — Page-Builder vs Systems Thinker

Every designer on a project has one of two internal monologues when they start working:

❌ Page-builder thinking

"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."

✅ Systems thinker

"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.

🏭 The Uniform Analogy

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.

02
Framework

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.

📖 From the Craft docs

"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

  1. Go to Marketplace → Pages → Craft and insert the Style Guide page.
  2. Open Global Root and customise the CSS variable values to match your client's brand.
  3. Map the semantic variables (--foreground-primary, --background-primary, etc.) to your brand colours.
  4. Start building using those predefined variables — never by hardcoding values.
💡 The key insight

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
🏗️ The Blueprint Analogy

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.

03
CSS Variables

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

--foreground-primary
Main body text, primary content
--foreground-secondary
Supporting text, subtitles
--foreground-accent
Links, highlights, CTAs in text
--foreground-muted
Captions, labels, disabled text
--foreground-border
Dividers, outlines, card edges

Background variables — surfaces, cards, pages

--background-primary
Main page background
--background-secondary
Alternate section background
--background-accent
Highlighted/featured background
--background-card
Card component background

Spacing variables — gaps and rhythm

--gap-xs
Tight spacing — within components
--gap-s
Small spacing — between elements
--gap-m
Medium — between components
--gap-l
Large — between sections

Interaction variables

--focus-color
Keyboard focus ring colour
--duration-default
Animation duration (e.g. 200ms)
--easing-default
Default transition curve
⚠️ The cardinal rule

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.

04
Core Concept

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 Coffee Cup Analogy

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

❌ Wrong token for role

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.

✅ Right token for role

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 PropertyToken FamilyWhy
color--foreground-*Text sits on top of a surface
background-color--background-*This property defines a surface
border-color--foreground-borderBorders 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
Exercise 4A Foreground or Background?
  1. Look at any webpage. Pick 10 elements at random.
  2. For each one, decide: is it a surface or is it drawn on top of something?
  3. Assign each to a Craft token: --background-card, --foreground-primary, etc.
  4. Notice how every single colour choice fits into one of these 9 variables. Nothing needs to be outside the system.
05
Token Types

Utility Tokens vs Semantic Tokens — and How Craft Names Them

Craft distinguishes between two types of tokens. This is official Craft doctrine — not opinion.

TypeCasingExamplesPurpose
Utilitykebab-casemargin-auto container buttonLayout and styling without conveying meaning about content
SemanticTitle CaseCard Pricing Table Team MemberConveys meaning and context about what the element IS
Variantkebab-caseis-button-secondary is-pricing-table-smallA modifier used WITH a base token — never alone
Sizekebab-casebutton-small button-largeT-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.

❌ Appearance-named (fragile)

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?

✅ Role-named (durable)

Card — always correct, regardless of colour or size

button + is-button-primary — always describes role

Section Hero — always describes position and purpose

📖 Craft doc quote — Variants

"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.

Exercise 5A Token Naming Audit
  1. Look at the tokens in any Webstudio project you have worked on.
  2. Identify any tokens named by appearance (colour, size, position).
  3. Rename each one to describe its role or purpose instead.
  4. For each semantic token, ask: "If I change the colour tomorrow, does this name still make sense?" If no → rename it.
06
Decision Framework

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.

📖 From the Craft docs — use a Token when:

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.

📖 From the Craft docs — use Local when:

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

  1. Will I use this combination of styles more than once? If yes → token.
  2. Is this a single property? If yes → CSS variable directly, or Local.
  3. Is this a simple toggle like text-align or display flex? Use the Style Panel button, not a token.
  4. Would a new team member need to understand this to work on the project? If yes → it needs a name → token.
📚 The Recipe Book Analogy

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.

07
CSS Units

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.

Visual comparison
16px
Fixed. Never changes.
1rem
= 16px by default. Scales with root font size.
1em
= parent's font size. Changes with context.
50%
= half of parent. Here parent = 320px demo width.

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 caseUnitWhy
Font sizesremAccessibility — scales with user preferences
Vertical spacing (margin, padding)remConsistent rhythm, accessibility-aware
Button internal paddingemScales proportionally with the button's font
Layout widths% or frFluid, responsive
Container max-widthpx or chHard limit — doesn't need to scale
Border widthpxShould be visually precise, not scale
Box-shadow blur/spreadpxVisual decoration — fixed is fine
Exercise 7A Unit Audit
  1. Open any project you've built. Look at 10 font-size values. How many use px?
  2. Convert each px font size to rem by dividing by 16. (24px1.5rem)
  3. Look at spacing (padding, margin, gap). How many use px vs rem?
  4. Check if any widths are fixed px where they should be % or fr.
  5. Rebuild one component using only rem for typography and spacing, % or fr for widths, and px only for borders and shadows.
08
Advanced Units

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 */
💡 clamp() replaces most breakpoints

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.

09
Layout Constraints

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;
❌ Fixed dimensions

width: 800px — broken on mobile

height: 400px — text overflows

font-size: 48px — too large on mobile

✅ Constrained fluid

width: 100%; max-width: 800px

min-height: 400px — grows with content

font-size: clamp(1.5rem, 5vw, 3rem)

10
Responsive Design

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

❌ "Fix it at the end" thinking

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.

✅ "Mobile-first fluid" thinking

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.

📖 Webstudio breakpoint rule

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);
Exercise 10A The Resize Test
  1. Build a 3-column card grid using auto-fit and minmax.
  2. Resize your browser from 1400px down to 320px without adding any breakpoints.
  3. The grid should reflow naturally — 3 columns, then 2, then 1. If it doesn't, the units are wrong.
  4. Add clamp() to the card title font size. Observe it scaling as you resize.
  5. Note: no breakpoint was added. The layout is responsive by nature of its units.
11
The Proof

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.

Client A — Light, Yellow Accent
Studio Agency
Welcome card content
background: var(--background-card)
color: var(--foreground-primary)
Get Started
bg: var(--background-accent)
Client B — Dark, Red Accent
Blue Corp
Welcome card content
background: var(--background-card)
color: var(--foreground-primary)
Get Started
bg: var(--background-accent)

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.
Exercise 11A The Brand Swap Test
  1. Build a Craft hero section using only Craft variables — no hardcoded colours.
  2. Set Global Root to Theme A: light background, yellow accent, dark text.
  3. Screenshot the result.
  4. Now change only the Global Root values to Theme B: dark background, red accent, light text.
  5. Screenshot the result.
  6. Compare the screenshots. Zero elements were individually restyled. This is the system working.
12
Practice

Alarm Bells — Recognise When to Stop and Apply Systems Thinking

🔔 Alarm Bell 1 — Typing a colour code directly

Stop. Ask: "Is there a Craft variable for this role?" There almost certainly is. Use var(--foreground-primary) not #050a1e.

🔔 Alarm Bell 2 — Using px for font-size

Stop. Convert to rem. Divide by 16. 24px → 1.5rem. Every font size should be rem unless you have a very specific reason.

🔔 Alarm Bell 3 — Fixed width on a container

Stop. Use width: 100%; max-width: [value]. Fixed widths break on smaller screens. Constraints are always better.

🔔 Alarm Bell 4 — Fixed height on any section

Stop. Use min-height. Sections must grow with their content. Fixed height causes overflow on every screen that doesn't match your design viewport.

🔔 Alarm Bell 5 — Copying-and-pasting the same styles

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.

🔔 Alarm Bell 6 — Using a background token for text colour

Stop. Text always gets a foreground token. If you are using --background-accent on a color: property, you are using the wrong token family.

🔔 Alarm Bell 7 — Naming a token after how it looks

Stop. dark-blue-box will be wrong the moment the colour changes. Name it by role: Card, Section Hero, button.

🔔 Alarm Bell 8 — Building without checking Craft Library first

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.

Quality Assurance

Pre-Handoff QA Checklist — Run This on Every Project

Craft & Variables
Craft library inserted from Marketplace — Global Root has all Craft variables
All brand colours mapped to semantic Craft variables (--foreground-primary, --background-primary etc.) in Global Root
Zero hardcoded colour values in any token — every colour uses var(--...)
Foreground variables used only for text, icons, borders — never as background-color
Background variables used only for surfaces — never as text color
Tokens
Utility tokens are kebab-case (margin-auto, container, button)
Semantic tokens are Title Case (Card, Pricing Table)
Variant tokens use is- prefix and are never applied without their base token
No token named after appearance — all names describe role or purpose
No duplicate styles that should be a shared token
CSS Units
All font-sizes use rem (not px) — accessibility-aware
Spacing (padding, margin, gap) uses rem or Craft gap variables
No fixed width on containers — using max-width with width: 100%
No fixed height on content sections — using min-height instead
Fluid font sizes use clamp() or Open Props fluid variables
px used only for borders, shadows, and truly fixed decorative values
Structure & Navigator
Page structure matches Craft standard: Page Wrapper → Slot (Nav) → Main → Sections → Container → Slot (Footer)
All Boxes have custom semantic names in the Navigator (no "Div 1021")
Sections prefixed with "Section " (e.g. "Section Hero", "Section Services")
Parent elements are plural, children are singular (CardsCard)
Responsive
Tested at 320px, 480px, 768px, 1024px, 1440px viewport widths
Grids use auto-fit / auto-fill with minmax where possible — minimal breakpoints
No text overflows or content clipping at any viewport width
The Brand Swap Test passes: changing 6 Global Root values updates the entire site correctly
A designer who understands Craft
can 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.