Magna
Learners and students
- Stack
- Tamagui + React Native Web, Material 3
- Posture
- Warm, generous, touch-first
Mobile-first at 360dp. Bottom navigation exists only at compact widths.
Design systems engineering · Great Learning
Great Learning had three products, one loosely held design system, and a growing share of front-end code written by AI agents. I split it into three first-class systems, extracted every component value from Figma, and moved the specs into the files agents already read. Now people and agents build from the same source.
Great Learning runs a learner app, a suite of internal staff tools, and a public marketing site. In practice, all three used Magna, the learner system, and the other two improvised around it. They don't share a runtime, a component vocabulary, or an audience, so each one drifted on its own.
Then AI coding agents arrived: Claude Code, Cursor, and PMs and designers generating UI from prompts. Without an authoritative source, an agent fills the gaps with values it makes up.
When a person guesses a hex value wrong, it's one bug. When an agent invents values every time, the system erodes at machine speed.
So the brief was never "make it prettier." It was: three surfaces, three first-class systems, one source of truth that humans and agents build from in the same way.
The founding decision: define each system by who it serves, not by a shared component list. No abstraction layer forcing them into a fake common API. Each system's audience decided its rules, down to the breakpoint.
Learners and students
Mobile-first at 360dp. Bottom navigation exists only at compact widths.
Staff, faculty and ops, all day on big monitors
Desktop-first, min 1024px. Tables scroll horizontally instead of silently reflowing.
Prospective learners, the public
Mobile-first is mandatory. At 360px, without scrolling, the hero answers: what is this, what do I get, what do I do.
Shared infrastructure on the outside, independent engines inside. The libraries never share code with each other.
The deepest work started with blunt feedback: the component definitions "weren't working." There were two separate failures, found months apart, and each taught a different lesson. One component, the Switch, shows the whole path.
The original definitions named components without defining them. "A button, primary variant." No border, no height, no radius, no padding.
Asked to build a Magna button, an agent had no choice but to invent values, and invented values are off-system by definition.
{ "name": "Switch", "variant": "default", "description": "A toggle switch" } // colour? size? states? the agent guesses.
The only honest source of a component's values is its Figma file, not the docs and not anyone's memory. A .fig file is a ZIP around a kiwi-encoded binary, so I built a decoder that unpacks it into a node tree: about 286,000 nodes for Magna alone.
Then came the real job: finding the component library in that tree and filtering out Android system kits, flag icons, glyphs, payment logos and archived canvases. Missing a real component and including a decoy break the output in the same way.
The first extractor read values off each component's outer frame. That was fine for Buttons and empty for Switch, Checkbox and Radio, whose colour lives deeper, on the track and thumb.
{ "name": "Switch", "width": 52, "height": 32 } // outer frame only. track, thumb and outline // are three levels down, unread.
I shipped v1 anyway, with a politely worded "limitations" note. Stakeholders pushed back hard on quality, and they were right.
Instead of defending the work, I put the evidence side by side: the flagship Button was rich, a third of the library was hollow, and three dimensions (interaction states, dark mode and per-component type) had never been specced at all. Naming that plainly put the work back on track.
Button colour size radius padding type Switch —————— size —————— ——————— ———— Checkbox —————— size —————— ——————— ———— Radio —————— size —————— ——————— ———— never specced, for any component: interaction states · dark mode · typography
The rewrite walks every descendant node, finds fills wherever they actually live, reads type from text nodes, and sorts variants into three groups: canonical, interaction state, and dark mode. It runs across all three systems: 85 components, including all 43 of Jedi's, decoded from its own Figma.
The JSON was now right, and agents still didn't use it. A JSON file in a folder is passive. Nothing prompts an LLM to open it mid-task, and nothing says its numbers are non-negotiable.
{ "track": { "fill", "outline", "borderWidth" }, "thumb": { "fill", "size" }, "states": { "hover", "pressed", "focused", "disabled" }, "dark": { …overrides }, "keyboard", "contrast", "responsive" } // complete. sitting in /components. unopened.
The instinct was to add more data. That was the wrong move. From the validated JSON I generated a markdown reference: variant × size tables with hex, tokens, metrics, states, dark mode, keyboard behaviour and computed contrast.
Then I put a mandatory-read rule and a component picker inside each system's Claude skill, the file that auto-loads when an agent starts work. The JSON stays underneath as the schema-validated source that generates the markdown, so the two can't drift.
## Components — read before building UI If a component is listed here, its values are law. If it's missing, say so and flag a deviation. | Component | Reference | |-----------|---------------------| | Switch | components.md#switch | | Checkbox | components.md#checkbox| … 1 hop from where the agent already is
Code panels are illustrative excerpts that show the shape of each definition; real values are omitted.
Before the pivot, the right values were two hops away and nothing pointed to them. After it, they sit inside the one file every agent reads first, written as a rule rather than a suggestion.
components.md
Variant × size tables, states, dark mode, keyboard, contrast
↑ generated from schema-validated JSON
The skill is what gets the system to an agent. Every system ships one in two parts, design rules and content rules, but they aren't copies of one template. Each system's purpose decides what comes first, and what is forbidden is spelled out as carefully as what is allowed.
The shared engagement protocol, embedded in all three skills.
The old docs described responsiveness and accessibility in prose, and nothing a machine could follow. Both became per-component fields. Contrast is computed rather than assumed, and AA failures are flagged inside the definition, which gave designers a concrete list to fix.
keyboard: Space → toggle Tab → move focus touchTarget: 48dp minimum contrast: computed per variant · AA fails flagged responsive: behavior: per system rule (360dp / 1024px)
The same idea applies to content. A first attempt copied Great Learning's whole writing-review workflow into the docs, including intake questions and output formats. The fix was to keep only the rules, nested under the right section. Where a rule sits mattered as much as what it says.
Each system now has a self-contained design-system doc covering colour and type, responsiveness, content rules, the AI protocol, the embedded component reference, and anti-patterns. Every definition holds exact dev values and plain-language guidance (what it is, when to use it, when not to) in the same file, so an engineer's spec and a PM's explanation can't fall out of sync.
For an AI consumer, correct data in a file it never opens is worthless. The win came from putting the truth where the agent already looks and framing it as a rule.
Every value traces back to a Figma file. Once a hex value is hand-waved, the system has started to drift.
The side-by-side of hollow and rich components moved the work further than any amount of polish. Owning a defect beats footnoting it.