Design files
What a Knighn design file is, and the rules an agent follows to make one.
Knighn
Knighn is a design tool. A Knighn design is one .html file. The source of the design is React TSX with Tailwind CSS v4 classes. The file opens in any browser and draws itself. The Knighn app shows the same file on a canvas, where people and agents edit it.
The design file
This is a complete design file. It has one component, one screen and two pages.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="knighn-format" content="1" />
<title>Launch</title>
<style type="text/tailwindcss">
@theme {
--color-background: #ffffff;
--color-foreground: #0a0a0a;
--color-accent: #007aff;
--color-accent-foreground: #ffffff;
}
</style>
</head>
<body>
<script type="text/tsx" id="knighn-source">
export function Button({ label = 'Get started', className = '' }: { label?: string; className?: string }) {
return (
<button type="button" className={`h-11 rounded-lg bg-accent px-5 font-medium text-accent-foreground ${className}`}>
{label}
</button>
)
}
export function Home({ className = '' }: { className?: string }) {
return (
<main className={`flex size-full flex-col items-center gap-6 bg-background px-6 py-24 text-foreground @3xl:px-16 ${className}`}>
<h1 data-name="Headline" className="text-5xl font-semibold tracking-tight">Ship it</h1>
<Button label="Start free" />
</main>
)
}
export const canvas = {
pages: [
{ name: 'Web', items: [{ render: Home, x: 0, y: 0, width: 1440 }] },
{ name: 'Components', kind: 'components', items: [{ render: Button, x: 0, y: 0 }] }
]
}
</script>
</body>
</html>
The parts of the file:
- The
styleblock holds the tokens: an@themeblock with the base values, and one block for each mode, such as[data-theme='dark']. See tokens.md. script#knighn-sourceholds the TSX. Each exported function is a component. A screen is a component too.export const canvasputs components on pages. Each item is a frame atxandy. With awidth, the frame is the container for container variants such as@3xl:.- Knighn writes the other head tags, the fonts link and the runtime script when it saves. Do not write them yourself.
- The last page of a design is the Archive page. It holds old ideas. Do not edit it or take screenshots of it unless the user tells you to. When the user tells you to keep the old version of a frame, move it there with
Archive(frame); do not delete it.
The source is a subset of TSX with no logic: no state, no events and no function calls. Knighn writes out a .map() over a literal array as JSX. See tsx.md.
The two tools
Knighn gives agents two MCP tools.
searchfinds anything: components, frames, layers, text, tokens, sections of this skill, styles, fonts, icons, brand logos, your files, recent errors and the screens of the Knighn app.executeruns a JavaScript snippet. The snippet reads and changes the design with helpers such asTree,Get,Insert,UpdateandScreenshot. See execute.md.
Three ways to work
The local app
The local server (knighn) edits the designs that are open in the Knighn desktop app. Your changes show on the canvas at once. Each execute call is one undo step with the name "Agent edit". The server works only while the app is open with a design.
Without document_id, a call works on the design that shows. GetAppState().designs lists every open design with its documentId. To work on more than one design, or on a design in a background tab, give document_id on every call. The tab does not change. Knighn runs the calls for one design one at a time, and each design keeps its own globals.
The hosted server
The hosted server (mcp.knighn.com, often called "Knighn" in Claude) edits the designs in the Knighn account of the user. It needs no app. Give fileId on each call: without it, a call uses the newest design, which changes when another agent saves. Find a file with search (kind file). Give fileId: "new" to make a new design, with a name for each new design when you make more than one. If the design changes while your call runs, Knighn runs the call again once on the new version. The user can archive a design: search marks it "archived", and GetAppState() lists it in archivedFiles, not in files. Prefer a design that is not archived. Do not edit an archived design unless the user tells you to. Bounds and screenshots need the Knighn daemon of the user to be online. To export files, the daemon must be online. GetAppState() tells you what the host can do.
The file
If the design is a local .html file and you can write files, you can edit the source and the theme directly. Knighn sees the change, reads the file again and merges it layer by layer. If the user changed the same layer on the canvas, your version wins, and the user can undo to get their version back.
- Change only
script#knighn-sourceand thestyleblock. Knighn owns the head tags and the runtime script. - Stay in the subset. Knighn shows errors in its code panel. When the app is open,
Lint()returns them. - The file has no layer ids. Knighn matches layers by position, tag,
data-name, text and classes. Keep thedata-nameof a layer that you move, so the match is exact. - Do not make a new design file by hand, because it needs the Knighn runtime. Make it in the app, or with
fileId: "new"on the hosted server.
Use the local server when the design is open in the app. Use the hosted server when the app is not open. Edit the file for large changes to the source when you have the file.
Links to the app
When the user asks how to do something in the Knighn app, give a link that opens the correct screen. Do not give only a menu path, such as Settings > Agents. Each link has three forms: mac for the macOS app, ios for the iOS app and web for the web app. On a Mac, give the mac link (knighn://go/…) first. Give the web link only when the user does not have the Knighn app.
searchwith the kindplacefinds the screen. Each result has itslinks.LinkTo('settings/agents')inexecutegives the same links.LinkTo('design/<file id>')gives the links to a design on Knighn. See LinkTo.
A first session
Use few, large calls. Each call is a round trip of some seconds, so a screen in three calls is much faster than a screen in ten. A new screen needs three or four calls:
| Call | What it does |
|---|---|
| 1 | Get the state, or start from a template. |
| 2 | Set the tokens and define all the components, in one snippet. |
| 3 | Add the whole screen, or two to three sections, with one AddScreen. |
| 4 | Look at one screenshot and the problems, then fix them all in one call. |
The TSX rules in short
A design runs no code, so the TSX is a subset of React. Read these before your first Define or AddScreen:
- A component is
export function Name({ label = 'Text', tone = 'primary', className = '' }: NameProps) { return (…) }. Props are strings, numbers, booleans,ReactNodeor unions of strings, each with a literal default. - No hooks, event handlers,
ref, spreads or function calls. Show each state as its own frame or variant.{autofix: true}onDefine,AddScreen,InsertandReplaceremoves them and says what it changed. - Conditions:
{p && …},{!p && …},{p === 'v' && …}, and||and&&of these. Write a JSX ternary as two conditions. - Classes: a string,
className={MAP[prop]},className={p ? 'a' : 'b'}, or a template literal of these parts: className={base ${tone[variant]} ${className}}. A nested ternary on one union prop becomes a variant map. - Text:
Build {build}or {Build ${build}}. - Lists:
.map()over a literal array (in place, or aconstlist) is written out as one item each. Other.map()calls are refused. - Icons:
<Icon name="arrow-right" className="size-4" />puts in a Lucide icon with noInsertIconcall. - To check new TSX before you apply it, call execute with
dryRun: true. It reports every problem and changes nothing. - When a call fails, send
editswith itseditId, not the whole snippet again.Patch(component, find, replace)changes part of a component that is already defined.
In the in-app chat, your first message has the state of the design and a summary of the TSX. In that case, do not call GetAppState, search or ReadSkill before your first edit. Start at call 2.
Get the state. The result gives the open file, its pages and frames, the components, the selection, and what the host can do (
capabilities). Get it one time. After that, each response gives the ids and names of what you made, so do not read them again.return await GetAppState()Start from a template when the user asks for a kind of page that a template has. Put it in, then change its copy and its tokens with
UpdateandSetTokens. The templates arelanding-page,pricing,dashboard,mobile-app,expo-swiftui,blog-post,portfolio,settingsandslide-deck.const { screens } = InsertTemplate('pricing', { page }) Print(Tree(screens[0], { depth: 3 }))With no template, set the look and make the parts that repeat as components, in one call. Use the brand of the user. With no brand, use the tokens of a style from
GetStyle(). Knighn puts the main of each new component on the canvas, beside the screens.const style = GetStyle('Calm SaaS') SetTokens(style.tokens) SetTokens(style.modes.dark, { mode: 'dark' }) Define(` export function Stat({ label = 'Customers', value = '2,400', className = '' }: { label?: string value?: string className?: string }) { return ( <div className={\`flex flex-col gap-1 \${className}\`}> <span className="text-3xl font-semibold tracking-tight">{value}</span> <span className="text-sm text-muted-foreground">{label}</span> </div> ) } `) return style.guidanceAdd the whole screen in one call. Give each section a
data-name. Use the components, and give each instance its own props.landing = AddScreen( 'Landing', ` <main className="flex size-full flex-col bg-background text-foreground"> <section data-name="Hero" className="flex flex-col items-center gap-6 px-6 py-24 text-center @3xl:py-32"> <h1 className="max-w-3xl text-5xl font-semibold tracking-tight text-balance @3xl:text-6xl">Invoices that pay themselves</h1> <p className="max-w-xl text-lg text-muted-foreground">Send, chase and reconcile in one place.</p> <Button label="Start free" /> </section> <section data-name="Stats" className="grid grid-cols-2 gap-8 px-6 py-16 @3xl:grid-cols-4 @3xl:px-16"> <Stat label="Customers" value="2,400" /> <Stat label="Invoices sent" value="1.2M" /> <Stat label="Paid on time" value="94%" /> <Stat label="Countries" value="40" /> </section> </main> ` ) await Screenshot(landing.item) return await Lint(landing.item)Check one time, at the end. Look at the screenshot and read the problems. Then fix all of them in one call. Put changes that do not depend on each other in the same snippet.
const [hero] = Find(landing.root, '[data-name="Hero"]') Update(hero, { classes: 'py-32' }) const [stats] = Find(landing.root, '[data-name="Stats"]') Update(stats, { classes: 'gap-12' }) await Screenshot(landing.item)
For a long page, add two to three sections in each call with Insert, and take the screenshot after the last one. Do not take a screenshot after each section.
The TSX in brief
You can write your first screen with these rules. Read tsx.md only when a diagnostic is not clear, and execute.md only for a helper that the tool description does not explain.
A component is
export function Name({ label = 'Save', tone = 'primary', className = '' }: Props) { return (…) }. Give each prop a literal default. Put${className}last on the root.A variant is a union prop and a map at the top level:
const cardTone = { plain: 'bg-surface', accent: 'bg-accent text-accent-foreground' }, used as${cardTone[tone]}.Show a part with
{flag && <X />},{tone === 'danger' && <X />}or{(open || pinned) && <X />}. Knighn reads a ternary as two conditions:{dark ? <A /> : <B />}, and a prop value of literals or props such astone={dark ? 'white' : 'brand'}, which becomes{dark && <Btn tone="white" />}and{!dark && <Btn tone="brand" />}.There is no state, event handler, spread or value import.
{autofix: true}onDefine,AddScreen,InsertorReplaceremoveskey,ref, handlers and hooks with a note. Knighn writes out a.map()over a literal array (in place, or aconstof literals) item by item, and reads{`Build ${build}`}asBuild {build}. For other repeated items, write each item, or one instance for each item. The snippet is JavaScript, so it can also use.map()to build the TSX string.Use tokens (
bg-accent,text-muted-foreground), container variants (@3xl:) andsize-full. Do not usemd:,h-screenorfixed.Icons: write
<Icon name="arrow-right" className="size-5" />in the TSX, or callawait InsertIcon(parent, 'arrow-right', { className: 'size-5' }). Brand logos:await InsertLogo(parent, 'Stripe', { className: 'h-8 w-auto' }), never an emoji or an icon. Images:await UploadImage(url), then<img src={…} alt="…" />.Component libraries:
UseKit('shadcn')orUseKit('heroui')defines Button, Badge, Card, Input, Switch, Checkbox, Tabs, Avatar, Separator and Tooltip with their variants. See components.md.States: show a hover, a tooltip or a confirmation step with
AddStateandAddTooltip. See tailwind.md.
General rules
- Make each screen a component with
AddScreen. Make each part that repeats a component withDefine, and use instances of it. - Use tokens for colours, fonts and radii:
bg-accent,text-muted-foreground,font-display,rounded-lg. Do not repeat hex values in classes. A token class is short:bg-surfaceis better thanbg-[#f5f5f7], and it changes with the theme. - Keep class strings short. Put a look that repeats in a component with a variant prop, then write
<Card tone="accent" />, not the same ten classes again. For a copy of a layer, useCopy, thenUpdateits text or props. - Lay out with flex and grid. Use
absoluteonly for overlays and decoration. - Use the Tailwind scales for spacing and type. A size that divides by 4 is a step:
max-w-280is 1120 px. Use an arbitrary value only for a value off the scale, such ash-[38px]ortext-[56px]. - Use container variants such as
@3xl:for responsive changes. Do not usemd:,h-screen,vhunits orfixed. See tailwind.md. - Build a whole screen, or two to three sections, in each
executecall. Take one screenshot when the screen is complete, not one after each section. - Put changes that do not depend on each other in one snippet: set the tokens and define the components in the same call, and fix all the problems of a screenshot in the same call.
- Do not read again what a response already gave you. A response lists the ids of new layers, and
AddScreenreturns{ component, item, root }. - Read each diagnostic and warning in the response, and fix it in the next call.
Lint()lists all problems. - Change the layers that exist with
UpdateandReplace. Do not delete a design to build it again. - Do only what the user asked. The user can edit at the same time as you. Before you change a layer that you read earlier, read it again with
Get. - When the user asks for alternatives, options or other versions of a screen, change the layout only. Keep the theme of the design: its tokens, colours, fonts, components and text. Make a new style only when the user asks for one.
- Write real words. Do not use lorem ipsum.
- Give each
imganalt. Keep the contrast of text at 4.5:1 or more.
Check each section for these problems:
- The layout collapses, or content goes outside its frame.
- Text is cut off, or a long word does not wrap.
- Text or icons do not have enough contrast with their background.
- Items do not align, or the spacing is not even.
- The screenshot does not show what you intended.
Style
- Do not make grids of identical cards again and again. Change the layout and the scale from section to section.
- Do not put each element in its own box. Use a container only when it has a structural purpose.
- Use gradients, shadows and large radii only when the style asks for them.
- Any Google font is available. SF Pro shows only on Apple devices.
- Do not draw logos, illustrations or mascots with paths. For a brand logo, use
InsertLogo(svgl). For other art, use an image from the user, or a placeholder with a label. - When the user gives no brand or style, start from
GetStyle(). - When the user wants a common page, such as a landing page, a pricing page, a dashboard or a slide deck, start from
InsertTemplate(id), then change it withUpdate.ListTemplates()gives the ids.
Reference
| File | Read it for |
|---|---|
| tsx.md | The TSX subset, what Knighn refuses, and how to fix each diagnostic. |
| execute.md | Each execute helper, with its arguments and its result. |
| api.d.ts | The execute helpers as TypeScript declarations. |
| tailwind.md | Tailwind v4 habits: scales, arbitrary values, container variants, dark mode. |
| tokens.md | @theme tokens, colour pairs and modes. |
| components.md | Props, variants, slots, instances and mains on the canvas. |
| assets.md | Fonts, images, icons, brand logos and alt text. |
| layout.md | Frame sizes and complete TSX recipes for common screens. |
| guide/landing-page.md | Landing pages and marketing sites. |
| guide/web-app.md | Web app screens. |
| guide/dashboard.md | Dashboards, tables and charts. |
| guide/mobile-app.md | Mobile app screens. |
| guide/expo-swiftui.md | iOS screens from @expo/ui SwiftUI controls, and their export to Expo code. |
| guide/slides.md | Presentation slides. |
| guide/design-system.md | Screens from the components and tokens of a design system. |
| guide/code.md | Moving a design into a React app. |
| guide/code-projects.md | Code projects: the screens of a code project, with no open tab. |