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 style block holds the tokens: an @theme block with the base values, and one block for each mode, such as [data-theme='dark']. See tokens.md.
  • script#knighn-source holds the TSX. Each exported function is a component. A screen is a component too.
  • export const canvas puts components on pages. Each item is a frame at x and y. With a width, 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.

  • search finds 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.
  • execute runs a JavaScript snippet. The snippet reads and changes the design with helpers such as Tree, Get, Insert, Update and Screenshot. 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-source and the style block. 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 the data-name of 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.

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.

  • search with the kind place finds the screen. Each result has its links.
  • LinkTo('settings/agents') in execute gives 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, ReactNode or 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} on Define, AddScreen, Insert and Replace removes 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 a const list) 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 no InsertIcon call.
  • 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 edits with its editId, 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.

  1. 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()
    
  2. 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 Update and SetTokens. The templates are landing-page, pricing, dashboard, mobile-app, expo-swiftui, blog-post, portfolio, settings and slide-deck.

    const { screens } = InsertTemplate('pricing', { page })
    Print(Tree(screens[0], { depth: 3 }))
    
  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.guidance
    
  4. Add 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)
    
  5. 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 as tone={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} on Define, AddScreen, Insert or Replace removes key, ref, handlers and hooks with a note. Knighn writes out a .map() over a literal array (in place, or a const of literals) item by item, and reads {`Build ${build}`} as Build {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:) and size-full. Do not use md:, h-screen or fixed.

  • Icons: write <Icon name="arrow-right" className="size-5" /> in the TSX, or call await 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') or UseKit('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 AddState and AddTooltip. See tailwind.md.

General rules

  • Make each screen a component with AddScreen. Make each part that repeats a component with Define, 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-surface is better than bg-[#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, use Copy, then Update its text or props.
  • Lay out with flex and grid. Use absolute only for overlays and decoration.
  • Use the Tailwind scales for spacing and type. A size that divides by 4 is a step: max-w-280 is 1120 px. Use an arbitrary value only for a value off the scale, such as h-[38px] or text-[56px].
  • Use container variants such as @3xl: for responsive changes. Do not use md:, h-screen, vh units or fixed. See tailwind.md.
  • Build a whole screen, or two to three sections, in each execute call. 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 AddScreen returns { 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 Update and Replace. 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 img an alt. 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 with Update. 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.