# Decide the switches first, and the folders follow

Before the first token, decide which switches the product needs, brand, light or dark, language, and let them shape the folders. Then the scaffold, GitHub, a check on every pull request and the first rules for the agent, one prompt at a time.

Disciplines: Design systems, Design engineering · Topics: Design systems, Design tokens, RTL, GitHub, AI coding agents

Source: https://www.ahmedamr.com/writing/design-system-repository-setup

Part 2 of 13 in *Build a design system with a coding agent*.

A design system's repository is the one place its decisions live, so its shape matters before anything goes into it. **The shape comes from two questions.** Which layers will the tokens have, and which switches will the product need? This part answers both, builds the empty structure with a coding agent, puts it on GitHub, and sets up the habit that keeps it honest, a check that runs on every change before it is accepted.

The starter repository at tag `part-2-setup`: https://github.com/ahmedamr-r/design-system-starter/tree/part-2-setup

## A switch changes how everything looks, and nothing it does

A switch is anything that changes how the whole product looks without changing what it does. A brand is a switch. So is dark mode, and so is a language whose type needs a different family. Most products have one or two, and **it is worth naming them before the first token**, because each one becomes a folder and an attribute, and adding one later means revisiting every file that assumed it was not there.

| Switch | What it changes | Set with | Folder |
| --- | --- | --- | --- |
| Brand | Whose product it is, its colours and sometimes its corners | `data-brand` | `tokens/brands/` |
| Mode | Light or dark, and later high contrast | `data-mode` | `tokens/modes/` |
| Language | The type a language needs, its family, weights and line height | `lang` | `tokens/locales/` |

Density, roomy or compact, is a fourth that some products need, and it works the same way with a folder and an attribute of its own. The starter plans the three in the table. A product that will only ever have one brand and one mode can leave those folders out, and nothing else in the series changes.

## Direction needs no tokens of its own

Right to left looks like a switch and mostly is not one. When a page turns right to left, almost no value changes. The card is as wide as before, its padding is as deep and its corners are as round. What changes is which side each thing sits on.

CSS can name a side in two ways. Physical properties name a side of the screen, such as `padding-left`, `margin-right` and `text-align: left`. [Logical properties](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_logical_properties_and_values) name a side of the reading direction, such as `padding-inline-start`, `margin-inline-end` and `text-align: start`. **Under `dir="rtl"`, every logical property moves to the other side by itself**, and so do flex rows and grids, so a row that starts on the left in English starts on the right in Arabic.

**One rule, two directions** · `feature.module.css`

```css
/* Right in English, wrong in Arabic */
.feature {
  padding-left: 12px;
  text-align: left;
}

/* Right in both */
.feature {
  padding-inline-start: 12px;
  text-align: start;
}
```

So direction gets no folder. The page sets `dir` next to `lang`, and components written with logical properties follow it. Part five builds that, along with the parts of right to left that do need decisions, such as icons that point somewhere, numbers inside an Arabic sentence, and testing both directions in every review.

## Every switch is a file of re-pointed answers

The mechanics are worth seeing before part three builds them. **The token layers hold one answer for every decision.** The card's surface is white, its text is near-black and its button corners are 8 pixels. Each switch folder holds files that give different answers to a few of those decisions, one file per option. Harbor's file says the brand colour is blue. The dark file says the surface is near-black. The Arabic file says the type uses another family.

The page says which options apply, with attributes. `data-brand="grove"` on an element picks Grove's file for that element and everything inside it, `data-mode="dark"` picks the dark file, and the two combine because neither file answers what the other one does. Any element can carry any switch, so a dark sidebar can sit in a light page, and a client's card can sit inside an agency's own dashboard.

*An interactive figure sits at this point on the page: the pricing card with brand, mode and language switches, the attributes they write, and a table of which file answers each of the card's decisions.*

- Surfaces and text: `semantics/color.json`, overridden by `modes/dark.json` in dark mode.
- Brand colour: the brand's file, `brands/harbor.json` or `brands/grove.json`, picks the hue, and in dark mode `modes/dark.json` picks a lighter step for links and tags.
- Button corners: `semantics/shape.json`, overridden for Grove by `brands/grove.json`, which makes its buttons pills through `components/button.json`.
- Type: `semantics/type.json`, overridden in Arabic by `locales/ar.json`, which re-points the family and the line height.
- Which side things sit on: no file. `dir` flips it, and components written with logical properties follow.

Which file answers each of the card's decisions, live. The brands are the starter's own. Dark mode and Arabic are the values parts four and five add, shown ahead of time.

That only works if every value stays connected to the one it came from, so that changing one answer reaches every screen that reads it. Part three shows what breaks when the build loses those connections, and the one setting that keeps them.

*In Figma.* A Theme collection with Light and Dark modes, where surface/default points at gray/0 in light and gray/900 in dark.

In Figma a switch is a variable collection with one mode per option, so a Brand collection with Harbor and Grove, a Theme collection with Light and Dark, and a Type collection with English and Arabic. A frame picks one mode from each collection, the way an element picks one file per attribute.

## The folders, before anything goes in them

Three folders hold the layers and three hold the switches. They all start empty, and **having every folder from the first commits means the agent never has to decide where a file belongs**. It finds the folder already there, with a README saying what goes in it.

**The repository at part-2-setup** · `design-system-starter`

```text
tokens/
  primitives/   raw values, named for what they are
  semantics/    jobs, each pointing at a primitive
  components/   empty until a brand restyles one component on its own
  brands/       one file per brand, picked with data-brand
  modes/        one file per mode, picked with data-mode
  locales/      one file per language, picked with lang
scripts/
  build.js      token files in, CSS variables out
  check.js      no checks yet
.github/
  workflows/    runs the build and the check on every pull request
AGENTS.md       how to work here, for people and agents
```

## Start with an empty folder, Node and Git

Everything from here happens in a terminal, the window where you type commands, and in the coding agent you start inside it. On a Mac that is the Terminal app, and on Windows it is Windows Terminal. Three tools need to be installed first. [Node.js](https://nodejs.org/en/download) runs the build and the check. [Git](https://git-scm.com/downloads) records every change, and a Mac offers to install it the first time you type `git`. The [GitHub CLI](https://cli.github.com) comes in later in this part, to create the repository without leaving the terminal.

```bash
node --version
mkdir design-system-starter
cd design-system-starter
git init
```

`node --version` should print 22 or higher. The next two lines make the folder and move into it, and `git init` turns it into a repository, so every change from here can be recorded. Then start your coding agent in that folder, the way its own instructions say, and give it the first prompt below.

**Commit after every prompt.** When the agent has finished and its explanation matches what you decided, ask it to save the work as a commit.

**Prompt** Commit the work

```text
Commit everything with a one-line message that says what changed.
```

Each commit in the starter is one prompt followed by that one, so your history can be compared with mine, commit by commit. Once the rules file exists at the end of this part, the agent commits on a branch and opens a pull request instead.

## An agent needs a folder, a rules file and one job at a time

A coding agent works in a folder of files. It reads them, writes them and runs commands, and it does what it is asked a little too readily. **Give it one job per prompt and ask it to explain what it wrote**, and the decisions stay yours. The first prompt sets up the project and nothing else.

**Prompt** Scaffold the token repository

```text
Set up a new project for a design token library. I'm a designer, so after you create each file, explain it in one or two plain sentences.

- Use Node and Style Dictionary 5 to turn token files into CSS custom properties.
- Token files are JSON in the W3C Design Tokens format, in four folders under tokens/: primitives, semantics, components and brands. Leave them empty. Components stays empty until a brand needs to restyle one component on its own.
- Add two npm scripts: build, which writes dist/tokens.css, and check, which does nothing yet.
- Add a short AGENTS.md saying what this repo is. Rules come later.

Don't create any tokens yet.
```

*A replayed agent session plays at this point on the page:*

> Set up a new project for a design token library. I'm a designer, so after you create each file, explain it… (full prompt above)

- Read `./`: An empty folder.
- Ran `npm install -D style-dictionary@^5`: Installed style-dictionary 5.6.1.
- Created `package.json`: The project's name and its two commands.
- Created `scripts/build.js`: Reads every token file, writes CSS variables.
- Created `tokens/primitives/ semantics/ components/ brands/`: Four empty folders, one per layer and one for brands.
- Created `AGENTS.md`: What this repo is. Rules come later.
- Ran `npm run build`: Wrote dist/tokens.css, an empty :root block, since there are no tokens yet.
- Said: package.json names the project, lists Style Dictionary as the one tool it needs, and holds the two commands you'll run, build and check.
- Said: scripts/build.js reads every JSON file under tokens/ and writes each token out as a CSS variable in dist/tokens.css. The four folders hold primitives, semantics, component tokens and brands, and components stays empty until a brand needs to restyle one component on its own.

A replay of the session behind the starter commit “Scaffold the token repo”, with its excerpts trimmed.

`package.json` names the project and holds its two commands. `scripts/build.js` hands every token file to Style Dictionary[^style-dictionary], an open-source tool that turns token files into code, and asks it for CSS variables[^css-custom-property] in `dist/tokens.css`. The folders under `tokens/` are the layers, still empty, and `components/` is made now on purpose so every repository built from the starter has the same folders. `AGENTS.md` is the rules file[^rules-file], which coding agents look for at the root of a repository. The token files will follow the format the [W3C Design Tokens Community Group](https://www.w3.org/community/design-tokens/) publishes, so any tool that reads the format can read them.

## The switch folders come next

The brands folder came with the scaffold. The next prompt adds the other two switches and gives each switch folder a short README, for whoever opens the folder next, person or agent, saying what goes in it and which attribute picks its files.

**Prompt** Add the switch folders

```text
Add empty folders for the two other switches this system will have: tokens/modes/ for light and dark, and tokens/locales/ for languages that need different type. Give each switch folder, brands included, a short README saying what goes in it and which attribute picks its files: data-brand, data-mode or lang. Say in the locales README that direction is not a token switch. Don't create any tokens.
```

*A replayed agent session plays at this point on the page:*

> Add empty folders for the two other switches this system will have: tokens/modes/ for light and dark… (full prompt above)

- Read `tokens/`: Four empty folders: primitives, semantics, components and brands.
- Created `tokens/modes/README.md`: One file per mode, picked with data-mode.
- Created `tokens/locales/README.md`: One file per language whose type differs, picked with lang.
- Created `tokens/brands/README.md`: One file per brand, picked with data-brand.
- Ran `npm run build`: Wrote dist/tokens.css. The READMEs aren't token files, so nothing in it changed.
- Said: modes/ and locales/ start empty, like the layers. Each README says which attribute will pick its files, so whoever adds dark mode or Arabic later knows where the file goes and how a page asks for it.
- Said: There's no folder for direction. When a page turns right to left the values stay the same and only their side changes, which is the components' job, done with logical properties.

A replay of the session behind the starter commit “Add the switch folders”, with its excerpts trimmed.

The locales README also says what does not belong there. Direction is the decision most likely to end up in the wrong place, as a pair of left and right tokens for every spacing value, and one sentence in the folder where someone would put them heads that off.

## GitHub keeps every change, with who made it and why

GitHub keeps the repository somewhere other than your laptop, records every change with who made it and why, and lets a change be read before it is accepted. Three words cover most of what a designer needs. **A commit is a saved change with a note. A branch is a line of changes kept apart from the main one until it is ready. A pull request asks to bring a branch into the main line, where the change can be read, discussed and checked.**

The agent can create the repository with GitHub's command-line tool, `gh`, as long as it is installed and you have signed in once with `gh auth login`, which opens GitHub in the browser to confirm it is you. If you have not, the prompt has the agent tell you that one command and stop there. Creating the repository in the browser and pushing to it works just as well.

**Prompt** Create the repository and push

```text
Create a public GitHub repository called design-system-starter with the GitHub CLI, connect this folder to it, and push the main branch. Explain each command in one sentence before you run it. If I'm not signed in to the GitHub CLI, tell me the one command to sign in and stop.
```

*A replayed agent session plays at this point on the page:*

> Create a public GitHub repository called design-system-starter with the GitHub CLI, connect this folder to it… (full prompt above)

- Said: gh auth status checks that you're signed in to GitHub's command-line tool, so the next command can create a repository in your name.
- Ran `gh auth status`: Signed in.
- Said: gh repo create makes the repository on GitHub, connects this folder to it under the name origin, and pushes the main branch, in one command.
- Ran `gh repo create design-system-starter --public --source=. --remote=origin --push`: Created the repository and pushed two commits.
- Said: Both commits are on GitHub now, the scaffold and the switch folders. Nothing in the folder changed, so this step has no commit of its own.

A scripted replay of the commands, with your-name standing in for your account. They change no files, so this step has no commit in the starter.

## Nothing reaches main without passing the check

From here on, no change goes straight onto the main branch. The agent works on a branch and opens a pull request, and a workflow in [GitHub Actions](https://docs.github.com/en/actions) runs the build and the check on it. **With one setting in the repository, a pull request whose check fails cannot be merged**, so a rule broken by a tired person or a confident agent is caught before it reaches anyone.

**Prompt** Check every pull request

```text
Add a GitHub Actions workflow that runs npm ci, npm run build and npm run check on every pull request and on every push to main, using the Node version in .nvmrc. Then tell me how to set up the repository so a pull request can't be merged until this check passes.
```

*A replayed agent session plays at this point on the page:*

> Add a GitHub Actions workflow that runs npm ci, npm run build and npm run check on every pull request… (full prompt above)

- Read `package.json`: build and check, the two commands the workflow will run.
- Read `.nvmrc`: 22, the Node version the workflow will use.
- Created `.github/workflows/check.yml`: Runs on every pull request and every push to main.
- Ran `npm ci && npm run build && npm run check`: The workflow's three steps, run here first. They pass.
- Said: GitHub runs this file by itself whenever a pull request opens or main changes. It installs exactly what package-lock.json lists, builds the tokens, runs the check, and marks the pull request with a tick or a cross.
- Said: To block merging until it passes, open the repository's Settings, choose Rulesets in the sidebar, and add a new branch ruleset that targets the default branch. Set its enforcement to Active, because a new one starts disabled, and turn on Require a pull request before merging and Require status checks to pass before merging, with the check called check.
- Said: The check is easiest to add once it has run, so push this on a branch and open a pull request first. Rulesets are free on public repositories; a private one needs a paid plan.

A replay of the session behind the starter commit “Check every pull request”, with its excerpts trimmed.

**The workflow** · `.github/workflows/check.yml`

```yaml
name: Check

on:
  pull_request:
  push:
    branches: [main]

jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-node@v7
        with:
          node-version-file: .nvmrc
      - run: npm ci
      - run: npm run build
      - run: npm run check
```

GitHub calls the setting a [ruleset](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/about-rulesets). Open the repository's Settings, choose Rulesets in the sidebar and add a new branch ruleset. Set its enforcement status to Active, because a new ruleset starts disabled, target the default branch, and turn on Require a pull request before merging and Require status checks to pass before merging, with the check called `check`. The check is easiest to find in that list once it has run, so the first pull request is also how you finish setting it up. Rulesets are free on public repositories, and a private repository needs a paid plan.

The check does nothing yet. Every part from here adds to it, so by the time the system is in use, the workflow is what says whether a change is safe to merge.

## The rules file starts with how to work

The rules file grows with the system. Its first version has no rules about tokens, because there are none yet. It says how to work in the repository, so every session starts the same way, whoever or whatever is doing the work.

**Prompt** Write the first rules

```text
Rewrite AGENTS.md with how to work in this repository. Keep it short:
- one job per prompt, and ask before starting a second
- work on a branch and open a pull request; never push to main
- run npm run build and npm run check before saying you're done
- after writing a file, explain it in two sentences a designer would follow
- when a rule here is unclear, ask instead of guessing
- where things go: the layer folders, the switch folders and the two scripts
```

*A replayed agent session plays at this point on the page:*

> Rewrite AGENTS.md with how to work in this repository. Keep it short… (full prompt above)

- Read `AGENTS.md`: Two sentences on what the repo is.
- Read `tokens/brands/README.md, tokens/modes/README.md, tokens/locales/README.md`: Which attribute picks each switch folder.
- Edited `AGENTS.md`: How to work, and where things go.
- Ran `npm run check`: No checks yet.
- Said: No script can check these five. They're habits, written down so every session starts with them, mine and the next person's.
- Said: Rules about tokens go under these as the tokens arrive, and the check grows with them.

A replay of the session behind the starter commit “Write the first rules”, with its excerpts trimmed.

Many coding agents look for [`AGENTS.md`](https://agents.md) at the root of a repository. An agent that looks for a file under its own name can be given that file with one line in it pointing here, so every tool reads the same rules.

From the next part on, every prompt ends with the agent opening a pull request. You read its explanation and the change on GitHub, the check runs, and you merge when both look right. **That loop is the whole review process**, and it is the same one a team of people uses.

**Every decision the series makes from here goes into this file and into the check together.** Part three adds the layers and how semantic colours are named, parts four and five the switches, and part seven the full naming grammar, each with the check that enforces it.

At `part-2-setup` the starter has six empty folders, a build that writes an empty file, a check with nothing to check yet, a workflow that runs both, and a rules file. Part three fills the layers with tokens.

## In this series

1. [A design system is a set of decisions, and the code is where they live](https://www.ahmedamr.com/writing/why-design-system-in-code.md)
2. Decide the switches first, and the folders follow (this part)
3. Design tokens in layers (soon)
4. Dark mode and many brands (soon)
5. Right to left, and every language (soon)
6. The tokens after colour (soon)
7. Naming, versions and shipping tokens (soon)
8. Components for the web (soon)
9. From components to screens (soon)
10. Components for mobile, in React Native (soon)
11. Documentation people and agents can read (soon)
12. Teaching agents to build with the system (soon)
13. Shipping it, adopting it and keeping it alive (soon)

[^style-dictionary]: **Style Dictionary.** An open-source tool that turns token files into code: CSS variables, JavaScript, iOS and Android values.
[^css-custom-property]: **CSS custom property.** A named value in CSS, written --name and read with var(--name). Often called a CSS variable.
[^rules-file]: **Rules file.** A Markdown file at the root of a repository that coding agents read before working in it, such as AGENTS.md.
