Skip to content

Platforms

Backend integrations

CSS, native, inline, PDF and optional Tailwind integration boundaries.

packages/toned-core/backends/README.md ↗Rendered from the package’s Markdown

Backends receive resolved field operations, their symbolic predicates and origins. The common plan owns normalization, token evaluation, platform specialization and source order. Backends lower those fields to their supported output vocabulary.

CSS variables and atomic classes

SSR, mounted CSS hosts and direct system.exec calls all consume the same compiled plan. The direct entry is a spelling adapter: it expands legacy flattened keys once per input object/platform, then uses the common compiler and resolver. It has no independent field resolver or cascade engine. Opaque grid/selector/class payloads remain separate extension operations; accepting an extension is explicit. Tokens with pseudoRules also retain a CSS effect operation, so a finite descendant or pseudo-element class survives even when the token emits zero own fields. Native and Tailwind reject these opaque CSS effects. Effects require generated finite classes; browser-conditioned effect tokens need an explicit selector rule rather than an unconditional atomic class.

The CSS lowerer preserves finite atomic classes and responsive classes for opted legacy tokens. Contested fields and symbolic conditions use inline parameter chains, with linear Boolean guards. Generated rule/predicate parameters unreachable from the final fields are removed; authored custom properties and class-consumed alpha parameters are preserved. Resolvers run once per selected occurrence; static class selection uses the preserved authored token value rather than trying to reconstruct it from resolved CSS fields. Per-part host updates use cached part, extension and source indexes.

Explicit descriptors retain source-order field precedence. Unnamespaced legacy systems have one deliberate compatibility policy: their historical media/state ladder remains inside each override layer, and root ancestor/sibling source rules retain their existing CSS channels. A legacy pseudo-only field with no resting write retains its old missing fallback (computed-invalid when inactive), including its effect on caller classes. Descriptor and advanced-query chains use revert-layer for an absent resting field. Keeping that distinction avoids silently changing existing application paint.

Browser conformance fixtures verify computed values across three viewport sizes, active/hover states, Boolean/container conditions, ancestor channels and the legacy pseudo-only fallback. The Tailwind fixture independently compares its compiled utility output with the descriptor CSS adapter.

Tailwind build and runtime

TypeScript
// Build entry only. `compile` belongs to the application's installed Tailwind.
import { compile } from 'tailwindcss'
import {
  createTailwindBackend,
  createTailwindRuntime,
} from '@toned/core/backends'
import { buildTailwind } from '@toned/core/build'
const profile = createTailwindBackend({
  id: 'app-utilities',
  mappings: [
    { field: 'display', value: 'flex', utility: 'flex' },
    { field: 'gap', value: 12, utility: 'gap-[12px]' },
  ],
  parameters: [
    {
      field: 'gap',
      utility: 'gap-[var(--app-gap)]',
      variable: '--app-gap',
      serialize: (value) => `${value}px`,
    },
  ],
})
const artifact = await buildTailwind(ui, profile, {
  sheets: [button, lazyDialog],
  tokens: daylight,
  source: '@tailwind utilities;',
  compile,
})
// Publish artifact.css and JSON.stringify(artifact.manifest).
// Runtime entry: import the JSON manifest and the shared system/profile definitions.
const backend = createTailwindRuntime(ui, profile, publishedManifest)
const renderer = createRenderer(ui, { backend, tokens: daylight })

For imports/plugins, pass a compiler closure that supplies Tailwind's normal loadStylesheet/loadModule options. Application CSS source owns themes, prefix, layers and external utilities. Toned does not silently choose a Tailwind scale or configuration. The injected compiler is called only at build time; the returned backend closes over the validated profile and declaration inventory, not compiler functions. createTailwindRuntime accepts the JSON manifest, checks system/profile identity and restores the bound adapter without emitting CSS or importing Tailwind. Pure renderers and mounted hosts reject an unbuilt profile; profile.resolve is only a low-level mapping/formatting helper. An unrelated CSS manifest does not satisfy the gate, and an explicitly supplied renderer manifest must match the bound backend's artifact. Serializer implementations must be versioned with assets, like token resolvers; function-source hashing cannot account for their captured dependencies. Do not execute buildTailwind in a React render or server request.

The build passes complete candidates to Tailwind and uses isolated @apply probes to check their actual declarations. A mapping must emit exactly one field and the declared literal value (numeric dimensions mean CSS pixels); a parameter utility must emit exactly its field with var(--declared-channel). Conditional utilities, shorthand utilities with additional declarations, !important, and expressions whose equality cannot be established by this literal profile are rejected. A different spelling can still be equivalent in a browser, but proving general CSS expression equivalence requires environmental assumptions this portable profile does not possess. Use exact arbitrary-value utilities or an explicit serializer instead of asserting that a theme's rem spacing equals a fixed pixel value.

Every sheet, including lazy sheets, is explicit. Every authoring branch is evaluated for mapping validation before runtime variants prune it. The base token set, ui.themes and additional themes: Tokens[] are checked. Unrestricted values need parameter channels; finite profiles cannot promise arbitrary future theme values. Unknown sheets/conditions fail with a build-inventory diagnostic. The manifest contains the system schema, candidates, declaration identities and CSS fingerprint; assertBuildArtifact catches asset/manifest drift before publication.

Conditional utility output

Local states and named/ad-hoc media/container conditions emit helper CSS ahead of rendering. A true atom has a valid empty custom property; a false atom has an invalid one. Complement channels implement NOT, products implement AND, and fallback parameters implement OR without expanding the predicate tree into DNF. State gates reset on every target, so an ancestor's state does not leak into a nested target.

Runtime resolution serializes each operation value once and builds the field's fallback chain in the common plan's order. Exactly one fixed parameter utility reads that chain. Overlapping predicates therefore retain Toned precedence even if Tailwind sorts utility CSS differently. An unconditional winner can select one finite utility directly. Theme/value changes update parameters; browser facts remain browser-evaluated. Cross-part states and explicit relations require the mounted part registry, because named parts alone establish no DOM ancestry; those facts update the same controller without an additional React render.

CapabilityBuilt Tailwind profile
Exact finite field valuesValidated complete utilities
Dynamic fields/theme valuesDeclared fixed parameter utility and serializer
Local state/media/container algebraPrecompiled helper gates and parameter utility
Overlapping rule precedenceShared plan order, independent of class order
Cross-part/relation factsSupplied by the mounted host registry
Classes-only browser conditionsRejected: arbitrary overlap needs a conditional parameter channel
Classes-only dynamic valuesRejected by classesOnly: true; use parameter utilities
General utility/shorthand inferenceRejected: utility names do not establish field equivalence
Native hostRejected: this output requires CSS utility classes
Arbitrary web selector/grid extensionRequires an adapter for that extension; no silent fallback

Caller className ownership is preserved during patches and cleanup. That does not promise an arbitrary caller utility wins a CSS conflict; applications own their CSS cascade policy. A class attribute's order has no such meaning.

The Tailwind browser conformance fixture uses actual Tailwind compilation, verifies prefix and rem/pixel mismatches, compares first paint against CSS output with JavaScript disabled, and exercises media, local state, container, boolean overlaps, dynamic values/themes and lazy stylesheets without adding CSS rules. tailwind-built.test.tsx checks host-driven class updates, caller ownership and unchanged React render counts.

Inline HTML and email

inlineBackend emits only a frozen style props bag containing literal web CSS values. Use createInlineRenderer with explicit theme tokens. It has no generated CSS dependency and does not install process-global state. Its validatePlan hook checks every compiled branch before variants are selected: browser facts and opaque CSS effects cannot silently disappear just because they are inactive. Custom backends can implement the same optional hook to reject unsupported authored plans. Platform specialization still precedes this check, so native-only branches are irrelevant to an inline web renderer. The inline profile is not a PDF/native adapter.

PDF point profile

pdfBackend / createPdfRenderer support the common static React PDF and Forme layout subset: flex layout, point/percentage dimensions, point spacing and borders, font and text properties, literal colors, opacity, and relative/absolute placement. The field and enum allowlists live in pdf.ts. Unsupported fields and values fail before reaching a document host. Numeric dimensions are passed through as points; CSS units and expressions are rejected. Native-only shadows/transforms, web classes, grid, CSS extensions and browser-dependent branches are unavailable. Use explicit variants for document choices. Font registration, pagination, SVG drawing, images, and PDF metadata remain owned by the document renderer.

Generated alpha-color compatibility

Alpha-capable atomic rules emit a plain color first, a color-mix(in oklab, …, transparent) fallback second, and the existing relative-color expression last. A browser that cannot parse relative colors retains the canvas or text color; with color-mix support it also retains the alpha modifier, including the source color's own alpha. Browsers with relative-color support use the final expression. This fallback contract covers generated atomic rules; raw authored CSS and inline conditional expressions retain their own browser requirements.