uxdsl ships its agent guide inside the package, at node_modules/uxdsl/docs/agent-guide.md, so it always describes the version the project has installed. Add one instruction to the project's own AGENTS.md (or the instruction file your agent reads), next to the project's own rules:
Before generating or modifying UXDSL UI, read
node_modules/uxdsl/docs/agent-guide.md and the active theme JSON.
Preserve configured roles and responsive behavior.
Adjust the path when node_modules is hoisted to a monorepo root. A link alone may not be fetched by every agent; a copy in the repository ages with every upgrade.
Preserve intent, not just the current computed value. A component names a token or a role; the theme decides what it is and how it changes per breakpoint. Two declarations that compute to the same pixels today can mean different things tomorrow:
| Do not replace… | …with | because |
|---|---|---|
density(4) | space(4) or 0.75rem | the density steps at each breakpoint; the space does not |
palette(primary.main) | color(purple.700) or a hex | the role follows a theme change and dark mode; a color does not |
@ds-typo(h2) | a fixed font-size | the role's size and line height change per breakpoint |
@ds-surface(contained) | its six resolved properties | the role is shared; a copy is not |
Before a change, decide its scope: one component (pick another existing token, or write a local CSS exception after the directive), a shared role or progression (edit the theme and check every consumer), or a threshold (edit breakpoints in the theme and rebuild). Never reference a token the theme does not define: the build fails with a UXD_* code, listed on Diagnostics.
Each block below is the guide for one primitive: what it is responsible for, how to choose it in a component, when to change the theme instead, and what to verify.
UXDSL builds on standard CSS. Spacing defines the base spacing values, and Density makes that spacing responsive. Prefer Density for component spacing, use Spacing when a stable value is intentional, and use CSS directly when finer control is needed. This is the recommended authoring approach, not a compiler restriction.
When generating or modifying UI with UXDSL:
spacing definitions and existing component conventions before selecting tokens. Read densities and breakpoints when responsive behavior is required.space(n) as a configured token reference, not a pixel count or arithmetic multiplier. Do not infer values from another framework’s scale.density(n) token for component spacing. Use space(n) directly when a stable value is intentional, and use CSS directly when finer control is required. Do not duplicate an existing Density progression locally.space(n) merely because it matches a Density token at the current breakpoint. Preserve the component’s intended responsive behavior.applyTheme. Do not hand-edit generated CSS as the source of truth.rem value can have a different pixel size when the root font size changes; space() itself does not create breakpoint rules.Decision rule: Prefer Density for component spacing. Use Spacing for intentional stable values and CSS for finer control. Change shared Spacing or Density definitions only for intentional system-level changes.
Given this theme excerpt:
{
"spacing": {
"2": "0.25rem",
"4": "0.75rem",
"6": "1.5rem"
}
}For this deliberate exception, the card padding and actions gap must remain stable across breakpoints. Reuse the configured Spacing value:
.card {
padding: space(4);
}
.actions {
display: flex;
gap: space(4);
}Both references resolve to 0.75rem. Avoid replacing them with a hardcoded 0.75rem when they should remain connected to the theme.
Make only this card’s padding smaller, and intentionally keep it stable across breakpoints using the application’s spacing scale.
space(2) is smaller than space(4).padding: space(2). Keep the shared value of spacing entry 4 intact because the request applies only to the card.If the request instead changes spacing entry 4 across the system, update its theme value, apply or compile the theme, and verify both direct consumers and dependent Density mappings.
Choose the token in the component. Define its value in the theme.
UXDSL builds on standard CSS. Spacing defines the base spacing values, and Density makes that spacing responsive. Prefer Density for component spacing, use Spacing when a stable value is intentional, and use CSS directly when finer control is needed. This is the recommended authoring approach, not a compiler restriction.
When generating or modifying UI with UXDSL:
spacing, densities, and breakpoints definitions before selecting spacing tokens.density(n) token by default for component spacing.space(n) directly when a stable value across breakpoints is intentional. Use CSS directly when finer control outside the configured Density behavior is required.density(n) as a reference to a configured responsive spacing token. Never assume density(4) equals space(4) at every breakpoint.Decision rule: Prefer Density for component spacing. Use Spacing for intentional stable values and CSS for finer control. Change shared Spacing or Density definitions only for intentional system-level changes.
Choose the token in the component. Define its responsive behavior in the theme.
Given this example theme excerpt:
{
"breakpoints": {
"xs": 0,
"md": 768,
"xl": 1280
},
"spacing": {
"4": "0.75rem",
"5": "1rem",
"6": "1.5rem"
},
"densities": {
"4": "xs(space(4)) md(space(5)) xl(space(6))"
}
}When a component should follow that configured responsive progression, prefer:
.card {
padding: density(4);
}This resolves according to the theme:
768px → space(4) → 0.75rem.768px up to, but not including, 1280px → space(5) → 1rem.1280px and above → space(6) → 1.5rem.Avoid recreating the same system behavior locally:
.card {
padding: xs(space(4)) md(space(5)) xl(space(6));
}The local form duplicates behavior already represented by density(4) and disconnects the component from future changes to that Density mapping. It still references the spacing scale.
Explicit local responsive spacing is valid when the component intentionally requires behavior that should remain independent from the configured Density mapping.
If asked:
Make this card's padding follow the application's responsive spacing.
The agent should:
padding: density(4).The component declares which responsive spacing token it uses.
The theme defines how that token behaves across breakpoints.
Breakpoints name the viewport widths where responsive rules begin to apply. The theme JSON defines the thresholds, components use those names for layout changes, and Density uses them to make Spacing responsive. Prefer Density for component spacing; use explicit breakpoint values for layout behavior and deliberate local exceptions.
Responsibility: preserve the shared responsive thresholds. Decide whether the request changes a component’s behavior, a Density mapping, or a system-wide threshold before editing configuration.
breakpoints. Do not import values from another framework or infer device types from breakpoint names.Decision rule: Change the component to change local behavior. Change a breakpoint to intentionally move a shared threshold. Change Density to adjust shared responsive spacing.
{
"breakpoints": {
"xs": 0,
"sm": 480,
"md": 768,
"lg": 1024,
"xl": 1280
}
}.layout {
display: flex;
flex-direction: xs(column) md(row);
}| Viewport width | flex-direction | Supplying rule |
|---|---|---|
| Below 768px | column | xs(column) |
| From 768px up to, but not including, 1280px | row | md(row) remains active through lg |
| 1280px and above | row | md(row) remains active unless a later rule overrides it |
Move the shared md transition to 800px across the application.
md. Confirm the request includes these consumers.breakpoints.md in the source JSON and apply it through the project’s build or runtime integration.md() rule.Breakpoints define when. Components and Density define what changes.
UXDSL builds on standard CSS. Colors define the available color values; Palette assigns colors to interface roles. Both are configured in the theme JSON. Prefer palette() when a component expresses a role such as primary action or surface. Use color() when a specific color token is intentional, and standard CSS when finer control is needed.
Responsibility: maintain the foundational color collection. Colors supply values to direct consumers and linked palette roles. Normally, semantic UI components consume Palette rather than selecting Colors directly.
colors and its families, shades and named entries. Treat shade numbers as keys, not calculated brightness or contrast guarantees.color(token) directly only when a specific color identity is intentional, such as a swatch representing that token.color() consumers, CSS variable references and runtime links from Palette. Equal hex values alone do not establish a dependency.applyTheme. Do not hand-edit generated CSS as a second source of truth.Decision rule: Prefer Palette for semantic component styling. Use Colors directly only when a specific color identity is intentional. Modify a Color token only when its direct and linked consumers are intended to receive the change.
Preserve intent, not just the current computed value. A Palette role and a Color token can look identical today while responding differently to tomorrow’s theme changes.
{
"colors": {
"blue": { "500": "#3b82f6", "700": "#1d4ed8" },
"white": "#ffffff",
"ink": "#0f172a"
},
"palette": {
"primary": {
"main": "color(blue.700)",
"contrast": "color(white)"
},
"surface": {
"main": "color(white)",
"contrast": "color(ink)"
}
}
}/* A swatch intentionally represents this Color token */
.blue-swatch {
background: color(blue.700);
}The swatch intentionally consumes blue-700 directly. In the JSON, primary.main also references that Color. Updating the Color changes both; reassigning the Palette role changes its consumers while the swatch keeps representing blue-700.
Update blue-700 across the theme, including palette roles linked to it.
colors.blue["700"], its direct consumers, and palette references to it.Colors define the values. Palette defines their roles. Components express the intended use.
UXDSL builds on standard CSS. Colors define the available color values; Palette assigns colors to interface roles. Both are configured in the theme JSON. Prefer palette() when a component expresses a role such as primary action or surface. Use color() when a specific color token is intentional, and standard CSS when finer control is needed.
Responsibility: maintain the semantic roles consumed by UI components. Palette expresses purpose, such as primary action or surface, independently of the current color assigned to it.
palette, relevant mode overrides and component conventions before choosing a role and variant. Primary does not inherently mean blue.palette(role.variant) when styling interface roles. Confirm the role, variant and any referenced Color exist in the intended theme.applyTheme patch. Review mode-specific assignments so they preserve the intended role.contrast is not automatic validation.Decision rule: Prefer Palette when styling interface roles. Reassign a Palette role when the meaning stays the same but its visual color should change. Keep components connected to the role.
Preserve intent, not just the current computed value. A Palette role and a Color token can look identical today while responding differently to tomorrow’s theme changes.
{
"colors": {
"blue": { "500": "#3b82f6", "700": "#1d4ed8" },
"white": "#ffffff",
"ink": "#0f172a"
},
"palette": {
"primary": {
"main": "color(blue.700)",
"contrast": "color(white)"
},
"surface": {
"main": "color(white)",
"contrast": "color(ink)"
}
}
}.primary-action {
background: palette(primary.main);
color: palette(primary.contrast);
}The action consumes primary.main, which currently references blue-700. Reassign the role to another defined Color to update its consumers while keeping their semantic declarations intact.
Change primary actions to use the existing blue-500 color.
blue-500 exists and inspect the primary role, its consumers and mode overrides.palette.primary.main to color(blue.500) in the intended theme scope.palette(primary.main); leave direct blue-700 references unchanged.Colors define the values. Palette defines their roles. Components express the intended use.
Responsibility: maintain shared text roles and their responsive behavior. Typography defines reusable visual text styles; components select the appropriate role without hardcoding the resolved values. HTML preserves document semantics.
Typography is the preferred shared abstraction for text styling. Preserve intent, not just the current computed value. Keep the configured role and its responsive behavior intact, just as you would preserve a Density token or a semantic Palette reference.
typography_details, its default fields, referenced fonts, spacing, and breakpoints before choosing or modifying a style. Treat these references as dependencies; inspect them without assuming they need to change.@ds-typo(role). Do not invent undefined roles or tokens.Decision rule: Choose the configured Typography role in the component. Define and evolve its visual and responsive behavior in the theme. Use local CSS only for intentional exceptions.
.page-title {
@ds-typo(h1);
}The component selects the h1 typography role. The theme controls its font family, weight, line height, spacing references and responsive size progression.
Avoid copying the currently resolved values when the component is intended to remain connected to the shared Typography role:
.page-title {
font-size: 2rem;
line-height: 1.2;
}These local values no longer follow changes to the shared role. They are appropriate only when that independence is an intentional exception.
Make page titles follow the application’s responsive typography.
h1.@ds-typo(h1) to the title selector while preserving appropriate HTML semantics.Responsibility: maintain shared edge treatments. Borders combine width, style and color. Components select a preset; Palette can preserve the semantic color dependency.
border(n) preset when its complete treatment matches the intent. Do not interpret n as a pixel width.border() takes the preset key only: a second argument is UXD_EDGE_ARGUMENT.Decision rule: Choose a shared Border for a shared edge treatment. Use local longhands for intentional exceptions. Change the theme only for a shared change.
For “make only the selected card's border dashed,” retain its preset and add a local dashed style after it. The shared variable changes do not reset that longhand. Check its responsive widths and confirm unselected cards keep their existing style.
Responsibility: maintain shared corner shapes and their responsive behavior. Preserve intent, not just the current computed value.
radii definitions, breakpoints and any referenced Spacing tokens. Reuse an appropriate configured preset.radius(n) when a component belongs to the shared shape system. Do not substitute the value observed at one viewport.radius(pill) for the built-in 9999px treatment and radius(circle) for 50%. Check dimensions; a rectangular box with 50% rounding is not a circle.Decision rule: Choose a configured Radius for shared corner styling. Define its progression in the theme. Use native CSS for deliberate independent shapes.
For “make every card using radius-2 rounder on desktop,” inspect all consumers and the configured desktop threshold, update that preset's progression, rebuild and verify mobile, boundary widths and desktop. Keep components using radius(2).
Responsibility: maintain shared depth treatments and their responsive behavior. Preserve intent, not just the current computed value.
shadows configuration, breakpoints and referenced color or spacing tokens before selecting a preset.shadow(key) for box shadows (the former elevation() alias fails as UXD_SYNTAX_REMOVED). Do not infer visual strength from a numeric key or confuse depth with z-index.Decision rule: Choose the shared Shadow treatment in the component. Define its appearance and responsive progression in the theme. Use local CSS for intentional exceptions.
For “make only this card less elevated,” inspect the actual preset values and choose an appropriate existing treatment for that card; leave the shared definition intact. For “soften shadow-2 across the product,” edit that preset, preserve its layers and references, apply the theme and inspect every affected consumer and breakpoint.
Responsibility: maintain shared container treatments by composing existing design-system roles and tokens. Preserve intent, not just the current computed value.
surfaces configuration, breakpoints and referenced Density, Radii, Palette, Borders and Shadows before selecting a role.@ds-surface(role). Do not rebuild its six properties locally merely because the result looks identical today.density(n) and radius(n). Do not infer measurements from the numeric key.Decision rule: Choose the container role in the component. Compose its shared visual decisions in the theme. Use optional arguments or native CSS only for deliberate overrides.
For “make all contained cards less elevated on desktop,” inspect the contained role and its consumers, modify that role's shadow progression, apply the theme and verify each relevant breakpoint. Keep components using @ds-surface(contained). For one exceptional card, retain the role and override its shadow locally.
Responsibility: preserve shared field roles and visual states while keeping form semantics in HTML and application logic. Preserve intent, not just the current computed value.
Decision rule: Choose the configured field role in the component. Maintain shared visual behavior in the theme; implement validation and interaction semantics separately.
For “make all search fields use a stronger error border,” inspect the search role and its consumers, change its invalid border in the theme, regenerate the needed CSS and verify native and aria-invalid states. Keep components using @ds-input(search). For one exceptional field, choose another role or deliberate local CSS.