Build for Dunea

Themes guide

Design tokens, fonts, the art-direction guide and section recipes, and how the Dunea AI applies a theme.

A theme is design tokens, fonts, and a written art-direction guide that the Dunea AI follows when it designs an app. It can also ship real, owner-editable sections (blocks) and page presets built from them. You do not need a server to publish a theme.

The tokens decide the palette, type and spacing. The guide is what makes a theme feel like a particular designer's work rather than a colour swap: it tells the AI how to lay out pages, treat images, move things and write.

How a theme is applied

When an owner applies your theme, the Dunea AI uses its apply_theme tool. That does three things:

  1. Writes the tokens as CSS. The app gets a platform stylesheet with your palette, fonts, radius, spacing and type scale as CSS custom properties. The app's existing classes (bg-background, text-foreground, bg-primary, border-border and so on) follow the theme with no other change.
  2. Loads your fonts from Google Fonts, with a <link> in the page head.
  3. Briefs the AI. The AI receives a compact summary: a table of your tokens and the classes and variables each maps to, your guide, and the names of your section recipes. When it builds a section that has a recipe, it fetches that recipe's full text first.
  4. Installs your sections, if the theme has blocks: they are written to frontend/src/blocks/theme/ (replacing the previous theme's), your presets become section groups for pages the app doesn't have yet, and the brief lists them so the AI places them instead of rebuilding them. See Sections and page presets.

From then on, when the owner asks for a new page or a redesign, the AI designs with your tokens and follows your guide.

When an install ends

If the owner uninstalls your theme, Dunea suspends the listing, or a paid theme's subscription lapses, Dunea takes the theme back out of the app: the platform stylesheet, its import and the Google Fonts links are removed, and the app falls back to its own styles. This only happens if your theme is the one currently applied; if the owner has since applied another theme, nothing changes. If the app's sandbox is asleep at the time, the cleanup happens the next time the app is opened. The pages the AI designed with your guide stay as they are. If the owner installs or pays again, they re-apply the theme in one step.

The brief the AI receives is kept to about 6,000 characters. If your token table, guide and recipe names together are longer, the guide is trimmed (at a paragraph break where possible). Put the most important direction first.

Tokens

tokens: {
  colors: {
    background: '#f6f2ec',
    surface: '#ede6dc',
    text: '#1c1917',
    muted: '#6b635a',
    primary: '#1c1917',
    primaryText: '#f6f2ec',
    accent: '#9a7b4f',
    border: '#d9cfc2',
  },
  darkColors: { /* the same eight keys, optional */ },
  fonts: { heading: 'Cormorant Garamond', body: 'Work Sans' },
  radius: 0,
  density: 'airy',
  typeScale: 1.5,
}

Colours

All eight colours are required, as #rrggbb hex.

TokenWhat it is forIn the app
backgroundThe pagebg-background, --dunea-color-background
surfaceCards, panels, subtle fillsbg-card / bg-muted, --dunea-color-surface
textBody and heading texttext-foreground, --dunea-color-text
mutedSecondary text: captions, labels, metadatatext-muted-foreground, --dunea-color-muted
primaryPrimary buttons and key actionsbg-primary, --dunea-color-primary
primaryTextText on primarytext-primary-foreground, --dunea-color-primary-text
accentA signature colour, used sparinglybg-[var(--dunea-color-accent)], --dunea-color-accent
borderRules, dividers, input bordersborder-border, --dunea-color-border

The app's own accent class (the subtle hover and selection tint) gets a soft wash of your accent. The full-strength accent is --dunea-color-accent.

Check contrast: text on background and on surface, muted on background, and primaryText on primary should all be comfortably readable. Aim for at least WCAG AA (4.5:1 for body text).

Dark mode

darkColors is optional. Include it to give the theme a dark palette, with the same eight keys. It applies when the page has the dark class on <html>, or follows the visitor's system setting when the app opts in. Omit it for a single-mode theme; the AI is told not to add a dark mode unless the owner asks.

Fonts

fonts.heading and fonts.body are required, fonts.mono is optional. Each must be one of these Google Fonts families (THEME_FONTS):

SansSerifDisplay and grotesqueMono
InterInstrument SerifSyneJetBrains Mono
GeistPlayfair DisplayBricolage GrotesqueSpace Mono
DM SansCormorant GaramondSpace GroteskGeist Mono
ManropeFraunces
Plus Jakarta SansDM Serif Display
OutfitLibre Baskerville
SoraLora
Work SansMerriweather
PoppinsEB Garamond
Montserrat
Raleway
Nunito
Lato
Source Sans 3
IBM Plex Sans

The grouping is for browsing; any family can go in any role. Headings (h1 to h6) and the font-heading class get the heading face; body text gets the body face; code gets the mono face, or a system monospace if you leave mono out.

Only these families are allowed so that every theme loads from one trusted source and renders the same everywhere. Some families ship few weights (Instrument Serif and DM Serif Display have one), so write your guide around the weights that exist.

radius

Corner radius in px, an integer from 0 (sharp) to 32. It sets the app's default radius (rounded-lg) and --dunea-radius.

density

The spacing rhythm:

ValueSection paddingGuttersSpacing steps
compact4rem1rem0.875×
comfortable6rem1.5rem1×
airy9rem2rem1.25×

The AI uses --dunea-space-section for vertical section padding, --dunea-space-gutter for horizontal gutters, and the steps --dunea-space-1 to --dunea-space-9 in between.

typeScale

The ratio between heading sizes, from 1.1 to 1.6. Each step up (xs, sm, base, lg, xl, 2xl, 3xl, 4xl, 5xl) multiplies the size by this ratio; 3xl and larger are fluid, so they shrink on small screens. Common choices:

  • 1.2 (minor third): quiet, dense interfaces and long reading
  • 1.25 (major third): a balanced default
  • 1.333 (perfect fourth) to 1.5 (perfect fifth): editorial, with big display type

Larger scales also get tighter heading line-height.

Writing a great guide

The guide is markdown, 200 to 12,000 characters. The AI treats it as the theme author's art direction and follows it for palette, type, layout, imagery, motion and tone. Anything in it that is not about design is ignored.

Write it like a brief to a designer who has never seen your work. Concrete, checkable instructions beat adjectives: "Separate sections with whitespace and a single 1px rule in the border colour, never with background stripes" is something the AI can do; "elegant and refined" is not.

A structure that works:

## Mood
Two or three sentences: what the site should feel like, and what it should never feel like.

## Grid and layout
- Column habits, margins, alignment, asymmetry
- How sections are separated

## Typography
- Which face at which steps, weights, tracking, line length
- What never to do ("Never bold the serif")

## Colour
How much of each colour appears, and where the accent goes.

## Imagery
Subjects, crops, light, grade, what to avoid.

## Motion
Durations, easing, what moves and what does not.

## Copy tone
Voice, sentence length, example button labels.

## Do
- Three to five specific habits

## Don't
- Three to five specific things to avoid

Tips:

  • Lead with what matters most. If the brief is too long, the end of the guide is trimmed.
  • Use the token names. "Buttons are solid primary with primaryText, square corners" connects your guide to the CSS the AI has.
  • Give numbers. Margins in vw, line length in characters, durations in ms, tracking in em.
  • Say what to avoid. "No gradients, drop shadows or icon grids" saves the AI from its defaults.
  • Stay general enough for any business. The same theme might dress a bakery and a law firm. Describe the system, not one site's content.
  • Plain markdown only. HTML-like tags in the guide are neutralised before the AI sees them.

Section recipes

sections holds up to 20 named recipes, each 40 to 3,000 characters of markdown. A recipe describes how to build one kind of section in your theme's style: a hero, a pricing table, a gallery, a product page. The AI sees the recipe names in its brief and fetches a recipe's full text before it builds that section.

sections: [
  {
    name: 'Hero: cover story',
    recipe:
      'Full-viewport section. Left 5 columns: section label in tracked small caps, then a two-line heading at the 5xl step with one italic word, then one sentence of body copy and one primary button. Right 7 columns: a single 4:5 portrait image bleeding off the right and top edges. On mobile the image comes first at 80vh, the text below.',
  },
],

Good recipes:

  • Name the section the way an owner would ask for it ("Pricing", "Team", "Product detail").
  • Describe structure (columns, order, proportions), then type steps, then spacing, then the mobile layout.
  • Refer to tokens and type steps (primary, accent, 5xl), not raw values that duplicate the tokens.
  • Cover the sections most sites need first: hero, features or services, testimonials, pricing, contact, footer.

Sections and page presets

Recipes tell the AI how to build a section. blocks go further: they ARE the sections, as React components with settings the owner edits in the Sections panel. A theme can ship up to 30, plus up to 12 presets (one section group per page type) built from them.

import { defineTheme } from '@dunea/plugin-sdk';
import { readFileSync } from 'node:fs';

export default defineTheme({
  id: 'quiet-paper',
  // …listing fields, tokens, guide, sections…
  blocks: [
    {
      name: 'cover-story',
      title: 'Cover story',
      kind: 'section',
      settings: [
        { key: 'label', label: 'Label', type: 'single_line_text', default: 'Issue 01' },
        { key: 'heading', label: 'Heading', type: 'single_line_text', required: true, default: 'A quieter way to shop' },
        { key: 'image', label: 'Portrait', type: 'image' },
      ],
      pageTypes: ['home', 'page'],
      code: readFileSync('./blocks/cover-story.tsx', 'utf8'),
    },
  ],
  presets: [
    {
      group: 'home',
      pageType: 'home',
      sections: [{ id: 'cover', type: 'theme/cover-story', settings: { heading: 'Made slowly, worn for years' } }],
    },
  ],
});
  • Preset instances reference your blocks as theme/<name>; validate rejects a preset that uses a block the theme doesn't have.
  • Presets never overwrite the owner's pages. A preset group is created only if the app has no group with that id. Instance ids that clash with existing ones are renamed.
  • Applying another theme replaces frontend/src/blocks/theme/ with that theme's blocks. Sections the owner placed from your blocks then render nothing until they are replaced, so the AI offers the new theme's equivalents instead of deleting them.
  • A theme's data (custom content types its sections read, like testimonial) is created in the owner's custom namespace when the theme is installed; types the owner already has are kept. See Custom data.
  • Block code follows the block rules: the import allowlist, no network access, styles through your own tokens. Any change to blocks or presets is reviewed by a person; it is never fast-tracked.

Validate and preview

npx -p @dunea/plugin-sdk dunea-plugin validate

validate checks the manifest exactly as the marketplace does, including the font allow-list and colour formats. The listing page shows your tokens as a preview swatch, and your section names, so make sure your screenshots show the theme applied to a real-looking site.

Continue with Publishing, review and guidelines.

On this page