Introduction

Breakpoints belong to the theme, not to your components.

The idea in one paragraph

Design decisions — spacing, type, color, borders, shadows, and how each of them changes at a breakpoint — live once, in a theme JSON. Components name them (density(4), palette(primary.main), @ds-button(contained)) and never write a breakpoint. The compiler turns that into ordinary CSS, refuses to emit anything it cannot resolve, and the same JSON is applied in the browser at run time and audited from the command line.

Theme uxdsl.theme.json, merged over the base theme

{
  "densities": {
    "4": "xs(space(4)) md(space(5)) xl(space(6))"
  }
}

UXDSL you write paradigm/card.uxdsl

.card {
  @ds-surface(contained);
  display: grid;
  padding: density(4);
  gap: xs(0.5rem) md(1rem);
}
.card h2 { @ds-typo(h2); }

CSS you get compiled by uxdsl; :root shows only these tokens

:root { --uxdsl__typography__h2-font-size: var(--uxdsl__space__6); }
@media (min-width: 480px) { :root { --uxdsl__typography__h2-font-size: var(--uxdsl__space__7); } }
@media (min-width: 1024px) { :root { --uxdsl__typography__h2-font-size: var(--uxdsl__space__8); } }
@media (min-width: 1280px) { :root { --uxdsl__typography__h2-font-size: var(--uxdsl__space__9); } }
:root { --uxdsl__density__4: var(--uxdsl__space__4); }
@media (min-width: 768px) { :root { --uxdsl__density__4: var(--uxdsl__space__5); } }
@media (min-width: 1280px) { :root { --uxdsl__density__4: var(--uxdsl__space__6); } }
.card {
  padding: var(--uxdsl__surface__contained-padding);
  border-radius: var(--uxdsl__surface__contained-radius);
  background: var(--uxdsl__surface__contained-bg);
  color: var(--uxdsl__surface__contained-color);
  border: var(--uxdsl__surface__contained-border);
  box-shadow: var(--uxdsl__surface__contained-shadow);
  display: grid;
  padding: var(--uxdsl__density__4);
  gap: 0.5rem;
}
@media (min-width: 768px) { .card { gap: 1rem; } }
.card h2 {
  font-family: var(--uxdsl__typography__h2-font-family);
  font-size: var(--uxdsl__typography__h2-font-size);
  line-height: var(--uxdsl__typography__h2-line-height);
  font-weight: var(--uxdsl__typography__h2-font-weight);
  letter-spacing: var(--uxdsl__typography__h2-letter-spacing);
  margin-block-start: var(--uxdsl__typography__h2-margin-block-start);
  margin-block-end: var(--uxdsl__typography__h2-margin-block-end);
}

density(4) is written once. The theme says it is space(4) on a phone, space(5) from md and space(6) from xl; the compiler writes that ladder once, as custom properties in :root and min-width media queries, and the card reads var(--uxdsl__density__4). Change the theme line and every component that names density(4) follows, with no component edited. The gap shows the other half: a component can still say what changes at a breakpoint, by naming the theme's breakpoints instead of writing pixel widths.

What UXDSL is

  • CSS with a few additions. A .uxdsl file is CSS, native nesting included, plus value functions (space(), density(), color(), palette(), radius(), border(), shadow(), and one per breakpoint, xs() … xl()), four directives (@ds-surface, @ds-button, @ds-input, @ds-typo) and a documented SCSS-style subset ($variables, @if/@else, @each over a list, @for, @mixin/@include, @content, @import). The Language page has the whole grammar.
  • A compiler with one pipeline. The CLI (uxdsl build), the PostCSS plugin (uxdsl/postcss), the Vite plugin and the Webpack loader all run the same compile(). The output is plain CSS: custom properties and min-width media queries, nothing at run time.
  • One theme model. One JSON, validated against one schema and merged over a reviewed base theme. The same file is compiled, applied in the browser with applyTheme, and audited with uxdsl theme --diff and uxdsl theme --contrast.
  • Validated. Every reference is checked at build time. An unknown token, breakpoint, role or a misplaced directive is a UXD_* error with file, line, column and a suggestion — never a silent fallback. See Diagnostics.

What UXDSL is not

  • Not Sass. It compiles the subset listed above; any other Sass feature fails with UXD_SCSS_UNSUPPORTED naming what to write instead.
  • Not free of media queries. The output is media queries; you stop writing them in components. They are viewport min-width queries: there are no container queries — use native @container next to UXDSL.
  • Not typed. The theme has a JSON Schema and every reference is checked at build time; the CSS itself is not type-checked.
  • Not fluid. Values step at breakpoints. The base theme has no clamp() scales (a clamp() is still a valid literal value).
  • Not a runtime styling library. Nothing needs JavaScript at run time. The optional runtime changes token values without a rebuild; a structural change — a new typography field, a new state, a moved threshold — is refused with UXD_THEME_STRUCTURE instead of half-applied.

Is it worth it?

"It is CSS variables plus media queries; I can write that." Yes — that is literally the output. UXDSL generates and checks that ladder from one definition per token, keeps the definitions in JSON so that the build, the runtime, the contrast audit and coding agents read the same thing, and turns a var() that would resolve to nothing into a build error. With five tokens, write it by hand.

Compared with

ToolWhere a responsive value livesWhat it has that UXDSL does notWhat UXDSL adds
Tailwind CSS v4On every element: p-3 md:p-4 xl:p-6Container queries, arbitrary variants, a large ecosystemThe progression is defined once, on the token, and every component that names it follows
Open PropsIn the token, as fluid clamp() scales; no build stepFluid scales, zero toolingRoles (surfaces, buttons, inputs), build-time validation, the theme as JSON
Panda CSS / vanilla-extractIn typed recipes and conditions; Panda can also express responsive tokensFull typing, recipes, container-query conditionsAuthoring in CSS with no code generation; an optional runtime; diagnostics with file and line
SassWherever you write the mixinA complete languageTheme tokens and roles with references checked against a schema; UXDSL does not pretend to be a language
Style Dictionary / DTCGIn the token pipeline, exported to many platformsThe DTCG format, export to iOS, Android and design toolsThe consumption side in CSS; its JSON is its own format, not DTCG

Not promised in 1.0

Container queries, fluid clamp() scales, DTCG import and export, modes other than dark, and editor completion of a project's own theme are recorded as possible additions. None is promised.