Styleframe Logo
Tooling

Using Styleframe with AI agents

Give a coding agent a loop it can trust, with check, inspect and strict builds, and the mistakes to watch for when it writes a Styleframe config.

Overview

Coding agents can write a Styleframe config for you, from the tokens and themes to the utilities and recipes. They work best when every change gets an answer they can read: the CLI tells them what your config registers, checks what they wrote and fails when something is wrong.

Think of a recipe that points at a misspelt token, a utility that never emits a class, or a dark theme that quietly swaps your brand colour. Each of these builds with exit code 0, so an agent that only looks at the exit code never finds out.

This page walks you through setting an agent up to get it right. We'll look at:

  • The loop: the commands an agent runs before and after each change, and what each one tells it.
  • Common mistakes: the ones that build without a word, and how to avoid each.

The loop

Let's start with the loop. We recommend running these commands in this order, before and after every change an agent makes to your config:

Inspect what's registered

styleframe inspect --json

The inspect command prints every variable, theme, utility, modifier and recipe your config registers, as one JSON document. The agent reads the token names and variant options from it, instead of reading the source or the generated CSS. Pass a section, like styleframe inspect recipes --json, to print only that part.

Check the change

styleframe check --typecheck --json

The check command loads the config, writes nothing and exits with 1 when it finds a problem: a reference to a token that doesn't exist, a theme that overrides a token you never defined, or a token defined twice. With --typecheck, it also runs the TypeScript your project installs on the config, so a default variant that isn't one of the recipe's options shows up in the same call. Each problem comes back as a diagnostic with a code, a path to where it was made and a suggestion when a token name is close.

Build

styleframe build

The build command prints the same problems as warnings, without the TypeScript ones, and exits with 1 when it fails, so a script or an agent can trust its exit code.

Next, turn the warnings into errors. With strict: true, the build fails on any problem that check reports without --typecheck, and writes nothing:

styleframe.config.ts
import { styleframe } from "styleframe";
import {
    useButtonRecipe,
    useDesignTokensPreset,
    useModifiersPreset,
    useUtilitiesPreset,
} from "@styleframe/theme";

const s = styleframe({ strict: true });

useDesignTokensPreset(s, { colors: { primary: "#4f46e5" } });
useModifiersPreset(s);
useUtilitiesPreset(s);
useButtonRecipe(s);

export default s;

The strict option defaults to false, so existing builds keep working while you fix what the warnings report. See strict for the details.

Pro tip: Tell the agent about the loop where it looks for instructions, like your project's AGENTS.md or CLAUDE.md. For example: "Before you edit styleframe.config.ts, run styleframe inspect --json. After each edit, run styleframe check --typecheck --json and fix every diagnostic it reports."

Common mistakes

Now let's look at the mistakes agents make most often. Some of them build without a warning, and check can't see them yet. Others stop the build with an error that doesn't say how to fix it, and they stop check too: it prints the error on stderr, exits with 1 and prints no JSON on stdout. Either way, it's worth telling an agent about them, or pointing it at this page.

Call every utility creator

The utility() function returns a creator, and only the values you pass to the creator become classes. A creator that's never called produces no CSS, and nothing reports it:

styleframe.config.ts
import { styleframe } from "styleframe";

const s = styleframe();
const { variable, ref, utility } = s;

const spacingMd = variable("spacing.md", "1rem");

const createMarginUtility = utility("margin", ({ value }) => ({
    margin: value,
}));

// Without this call, there's no _margin class at all.
createMarginUtility({ md: ref(spacingMd) });

export default s;

A recipe that uses a utility generates the classes it needs itself, so this only bites the utilities you use directly in your markup. See Utilities for both ways to call a creator.

Load the design tokens before the presets that use them

The utilities preset references the design-tokens preset's token names, like @z-index.base and @border-style.solid, and so do the theme's recipes, like @color.gray-200. Without those tokens, the utilities preset stops the build at the first missing one:

Error: [styleframe] Variable "z-index.base" is not defined. Check that the variable exists before referencing it with "@z-index.base".

It reports one missing token per build, so working through them one at a time takes several attempts. It's best to call useDesignTokensPreset() first, with your own values on top of its defaults, as in the strict example above. For the same reason, keep its meta.merge at true: with merge: false, the preset drops the defaults it references itself, like color.gray-200, and fails the same way.

Check the dark theme the preset adds

The design-tokens preset adds a dark theme by default, even when you don't pass themes. That theme sets its own primary, secondary and other colours, so with only colors: { primary: "#4f46e5" }, dark mode shows the preset's own primary, #007A99, instead of yours. Run styleframe inspect themes --json to see every theme your config emits.

To keep your brand in dark mode, set the dark colours too. A colors object under themes.dark replaces the preset's whole dark colour set, so start from darkModeColorValues to keep its background, surface and text colours:

styleframe.config.ts
import { styleframe } from "styleframe";
import { darkModeColorValues, useDesignTokensPreset } from "@styleframe/theme";

const s = styleframe();

useDesignTokensPreset(s, {
    colors: { primary: "#4f46e5" },
    themes: {
        dark: { colors: { ...darkModeColorValues, primary: "#818cf8" } },
    },
});

export default s;

Write token references with @

A string value is either a token reference or an arbitrary value, and only the @ at its start tells them apart. Take a margin utility, called with a token and an arbitrary value:

styleframe.config.ts
import { styleframe } from "styleframe";

const s = styleframe();
const { variable, utility } = s;

variable("spacing.md", "1rem");

const createMarginUtility = utility("margin", ({ value }) => ({
    margin: value,
}));

createMarginUtility(["@spacing.md", "2rem"]);

export default s;

Here's what each form of value gives with this utility:

ValueRead asClassCSS
"@spacing.md"a reference to the spacing.md token_margin:spacing.mdmargin: var(--spacing--md)
"2rem"an arbitrary value_margin:[2rem]margin: 2rem
"spacing.md"an arbitrary value_margin:[spacing.md]margin: spacing.md
"[4px]"an arbitrary value, wrapped again_margin:[[4px]]margin: [4px]

The last two rows are mistakes that build without a warning, and a browser drops both declarations. Write a token path with its @, and an arbitrary value without brackets, since Styleframe adds them to the class name itself. Recipe declarations read values the same way, so padding: "[4px]" in a recipe gives padding: [4px] too.

In a utility created with an array, like the one above, or in a recipe, a reference with @ to a token that doesn't exist is the one mistake here that check does catch, as unresolved-reference. In a selector, a theme declaration or a utility created with an object, the same reference throws when you define it, so the build stops there.

Register modifiers before a recipe uses them

A recipe can style a pseudo-element with a key like &:after or &::after, or a state with &:hover. Each key needs its modifier registered first, or the build fails:

Error: [styleframe] Modifier "after" not found in registry. Make sure the modifier is registered before using it in a recipe.

The useModifiersPreset() function registers the pseudo-element modifiers along with the others. To register only those, call usePseudoElementModifiers():

styleframe.config.ts
import { styleframe } from "styleframe";
import {
    useDesignTokensPreset,
    useModifiersPreset,
    useUtilitiesPreset,
} from "@styleframe/theme";

const s = styleframe();
const { recipe } = s;

useDesignTokensPreset(s);
useModifiersPreset(s);
useUtilitiesPreset(s);

recipe({
    name: "card",
    base: {
        position: "relative",
        "&::after": { opacity: "0.5" },
    },
});

export default s;

Colour levels are fixed values

The design-tokens preset turns each colour into levels, shades and tints, like color.primary-50 and color.primary-shade-50. These are fixed oklch() values, computed when the config builds, not oklch(from var(--color--primary) …). So overriding --color--primary on its own, in your CSS or in a theme(), leaves its levels as they were:

styleframe.config.ts
import { styleframe } from "styleframe";
import { useDesignTokensPreset } from "@styleframe/theme";

const s = styleframe();
const { theme } = s;

useDesignTokensPreset(s, { colors: { primary: "#4f46e5" } });

// Overrides color.primary only. color.primary-50 and the rest keep their values.
theme("brand", (ctx) => {
    ctx.variable("color.primary", "#e11d48");
});

export default s;

To change a colour together with its levels, pass the new value to the preset instead, at the root or under themes, as in the dark theme example. The preset then computes the theme's levels from it.

Register utilities before recipes

A recipe turns each declaration into a utility class, so the utilities it uses must be registered before it. A recipe defined first fails the build:

Error: [styleframe] Utility "display" not found in registry. Make sure the utility is registered before using it in a recipe.

Keep the order of the examples on this page: design tokens, then modifiers and utilities, then recipes.

Best Practices

  • Run check after every change: it reports every problem in one pass, so the agent fixes them all at once instead of one per build.
  • Turn on strict once your config is clean: a build that fails on a problem is one an agent can't miss.
  • Read the registry before writing: styleframe inspect --json gives the real token names and variant options, which is better than guessing them from the docs.
  • Read the output as well as the exit code: for the mistakes above, open the generated index.css and look for the classes and values you expect.

FAQ