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:
- 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-borderand so on) follow the theme with no other change. - Loads your fonts from Google Fonts, with a
<link>in the page head. - 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.
- Installs your sections, if the theme has
blocks: they are written tofrontend/src/blocks/theme/(replacing the previous theme's), yourpresetsbecome 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.
| Token | What it is for | In the app |
|---|---|---|
background | The page | bg-background, --dunea-color-background |
surface | Cards, panels, subtle fills | bg-card / bg-muted, --dunea-color-surface |
text | Body and heading text | text-foreground, --dunea-color-text |
muted | Secondary text: captions, labels, metadata | text-muted-foreground, --dunea-color-muted |
primary | Primary buttons and key actions | bg-primary, --dunea-color-primary |
primaryText | Text on primary | text-primary-foreground, --dunea-color-primary-text |
accent | A signature colour, used sparingly | bg-[var(--dunea-color-accent)], --dunea-color-accent |
border | Rules, dividers, input borders | border-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):
| Sans | Serif | Display and grotesque | Mono |
|---|---|---|---|
| Inter | Instrument Serif | Syne | JetBrains Mono |
| Geist | Playfair Display | Bricolage Grotesque | Space Mono |
| DM Sans | Cormorant Garamond | Space Grotesk | Geist Mono |
| Manrope | Fraunces | ||
| Plus Jakarta Sans | DM Serif Display | ||
| Outfit | Libre Baskerville | ||
| Sora | Lora | ||
| Work Sans | Merriweather | ||
| Poppins | EB 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:
| Value | Section padding | Gutters | Spacing steps |
|---|---|---|---|
compact | 4rem | 1rem | 0.875× |
comfortable | 6rem | 1.5rem | 1× |
airy | 9rem | 2rem | 1.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 reading1.25(major third): a balanced default1.333(perfect fourth) to1.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 avoidTips:
- 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
primarywithprimaryText, 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>;validaterejects 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, liketestimonial) is created in the owner'scustomnamespace 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
blocksorpresetsis reviewed by a person; it is never fast-tracked.
Validate and preview
npx -p @dunea/plugin-sdk dunea-plugin validatevalidate 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.