Skip to content

[BDX Analog] Community-maintained style pipeline for AnalogJS #1

@benpsnyder

Description

@benpsnyder

Summary

This issue tracks the community-maintained path for a generic, strongly typed style pipeline that works well with AnalogJS while staying aligned with maintainer feedback in First-class Style Dictionary support analogjs/analog#2227.

The implementation home for this work should be this repo, not Analog core.

Why This Lives Here

In First-class Style Dictionary support analogjs/analog#2227, Brandon Roberts recommended:

  • keep style-dictionary and its dependency tree out of Analog core
  • avoid adding a large new public API surface to @analogjs/platform
  • avoid hard-coding library-specific target contracts into Analog
  • prefer a community-owned Vite plugin built on Vite's native plugin APIs
  • keep Angular runtime theming concerns in userland unless real framework gaps later prove otherwise

That is the right boundary.

This issue keeps the implementation energy, but moves it to the correct ownership model.

Goal

Build a community-owned style pipeline that gives Analog users excellent DX without requiring Analog itself to own:

  • Style Dictionary
  • Panda CSS
  • Tokiforge runtime theming
  • PrimeNG / Spartan / daisyUI / MUI / Web Awesome / Zard / other library-specific token schemas

Design Principles

  • Vite-native first: use normal Vite plugin hooks wherever possible
  • Analog-friendly: work cleanly in Analog apps without requiring a large Analog core API
  • Strongly typed config: TypeScript-first authoring with a small ergonomic configuration surface
  • Pluggable providers: Style Dictionary, Panda CSS, Tokiforge, and custom providers should all fit the same contract
  • Do not reinvent Tailwind: Tailwind should continue to own utility generation, theme semantics, and CSS pipeline behavior it already handles well
  • Standard schema aware: support standard token interchange formats where it helps, without forcing JSON-first authoring
  • Framework-light, ecosystem-heavy: keep host framework hooks minimal and let providers/adapters own the richer semantics

Proposed Shape

stylePipeline({
  providers: [
    styleDictionaryProvider({
      configFile: 'style-dictionary.config.ts',
    }),
    pandaProvider({
      configFile: 'panda.config.ts',
    }),
    tokiforgeProvider({
      configFile: 'tokiforge.config.ts',
    }),
  ],
})

The important API is the generic pipeline, not a token-engine-specific top-level config.

What This Should Provide

Capability Description
Typed config loading TypeScript-first provider configuration with strong inference
Community-owned build cache Generated outputs managed outside Analog core
Default CSS injection Select outputs can be injected as app-level CSS bundles
Virtual CSS modules Generated CSS can be exposed as imports where useful
Grouped output metadata Providers can expose outputs by tags or categories for downstream consumers
Stable emitted-file metadata Deterministic order, importId, and file metadata for Angular-friendly tooling and SSR integrations
Scoped diagnostics Pipeline-level debug scopes for generation, invalidation, and output wiring

Public API Direction

Top-level config

Surface Purpose
stylePipeline() Generic orchestration entrypoint
defineStylePipelineConfig() Typed helper for authoring config files

Provider layer

Surface Purpose
styleDictionaryProvider() Token transforms and output generation
pandaProvider() Panda coexistence and generated artifact registration
tokiforgeProvider() Angular-friendly theme/runtime integration patterns
customProvider() Escape hatch for community-defined pipelines

Output model

Providers should converge on a common typed output contract that can represent:

  • CSS outputs
  • theme outputs
  • manifests
  • TypeScript metadata/codegen artifacts

And each output should be able to describe:

  • id
  • kind
  • scope
  • order
  • absolutePath
  • rootRelativePath
  • importId
  • inject
  • tags
  • provider-specific metadata

Why This Is A Framework-Adjacent Problem

The implementation should live in a community repo, but the problem itself is still closely tied to framework behavior once generated styles participate in dev, build, SSR, and HMR.

Framework Public source Relevant snippet / API Why it matters here
Astro Astro CSS Vite plugin "This plugin tracks the CSS that should be applied by route." Astro treats CSS as framework-managed runtime state, with virtual modules and route-aware tracking. Token outputs create the same class of problem once they participate in dev, SSR, and build.
Nuxt Nuxt components module addTemplate, addPluginTemplate, addTypeTemplate, addVitePlugin Nuxt handles first-class framework features through generated artifacts plus framework hooks. That informs the generated manifest and emitted-output direction here.
Angular Angular language service adapters "templates and stylesheets" Angular already treats styles as compiler resources with dedicated reading, resolution, and invalidation behavior. The community pipeline should respect that reality rather than treating tokens as a disconnected prebuild script.

The key distinction is ownership:

  • the problem space is real and framework-adjacent
  • the implementation home should still be community-maintained unless real usage later proves Analog needs a tiny generic hook

Why This Fits Real Design Systems

The strongest design-system examples still converge on CSS custom properties as the durable runtime contract, even when the authoring and consumption layers above them differ.

System Public source Relevant snippet / API What it reinforces
Web Awesome Design Tokens overview and Color tokens "These custom properties thread through all of Web Awesome's components" and "CSS custom properties" A serious component system wants framework-agnostic CSS variables that can flow through every component and theme surface. The pipeline should make that easy to generate and serve.
Zard UI LLMs index and Theming guide "Using CSS Variables and OKLCH colors for theming." and "CSS variables (recommended)" Tailwind-oriented UI systems still center tokens around CSS variables, then map them into Tailwind's theme layer. That supports keeping Tailwind semantics in Tailwind while the pipeline owns generation and delivery.
Spartan Spartan theming "Customize your design system with CSS variables. No theming API required." and @theme inline Spartan makes the modern pattern explicit: semantic CSS variables are the source of truth and Tailwind is the mapping layer.
PrimeNG styled mode PrimeNG styled theming "A theme consists of two parts; base and preset." and "primitive, semantic and component" tokens PrimeNG already models a token architecture with layered presets, dark mode selectors, CSS variable prefixes, and CSS layers.
PrimeNG unstyled and Tailwind integration PrimeNG Tailwind integration, Pass Through API, and unstyled theming entry "It is designed to work both in styled and unstyled modes." and "Your Components, Not Ours" PrimeNG's headless direction shows the same requirement from the opposite side: the pipeline should support token delivery and adapter CSS while libraries own DOM customization and utility semantics.

Architectural Split

Layer Responsibility
Community style pipeline core Output lifecycle, virtual module identity, watch/HMR invalidation, typed provider contracts
Style Dictionary Token transforms, format generation, typed config-driven outputs
Panda Compiler/codegen authority for Panda-native workflows
Tokiforge Angular runtime/theme-service patterns on top of generated CSS variables
Tailwind Utility and theme semantics that Tailwind already handles exceptionally well
Design-system packages Framework bridge CSS and adapter outputs for daisyUI, Spartan, PrimeNG, MUI, Web Awesome, Zard UI, and similar ecosystems
Analog core Documentation and, only if later proven necessary, tiny generic style/resource integration hooks

Example Shapes This Enables

1. Base token bundle injected at app level

:root {
  --brand-primary: oklch(0.62 0.18 264);
  --brand-primary-foreground: oklch(0.98 0.01 264);
  --brand-radius-md: 0.75rem;
}

[data-theme='dark'] {
  --brand-primary: oklch(0.78 0.15 264);
  --brand-primary-foreground: oklch(0.12 0.01 264);
}

2. Tailwind, Zard, or Spartan-style bridge CSS from the same tokens

@theme inline {
  --color-primary: var(--brand-primary);
  --color-primary-foreground: var(--brand-primary-foreground);
  --radius-md: var(--brand-radius-md);
}

3. Web Awesome-style semantic token bridge from the same source

:root {
  --wa-color-brand-fill-loud: var(--brand-primary);
  --wa-color-brand-on-loud: var(--brand-primary-foreground);
}

4. PrimeNG styled-mode bridge from the same source

:root {
  --p-primary-color: var(--brand-primary);
  --p-primary-contrast-color: var(--brand-primary-foreground);
  --p-border-radius: var(--brand-radius-md);
}

5. PrimeNG unstyled or headless Tailwind bridge

@theme inline {
  --color-primary: var(--brand-primary);
  --color-surface-0: var(--brand-surface-0);
  --color-surface-900: var(--brand-surface-900);
  --radius-border: var(--brand-radius-md);
}

6. Framework-aware consumption in an Analog app

import 'virtual:bdx-style-pipeline.css';
import pipeline from 'virtual:bdx-style-pipeline';

await import('virtual:bdx-style-pipeline/tailwind.css');
await import('virtual:bdx-style-pipeline/web-awesome.css');
await import('virtual:bdx-style-pipeline/primeng-styled.css');

console.log(pipeline.outputs);
console.log(pipeline.getOutputsByTag('primeng'));

Compatibility

This design is intentionally pluggable rather than turning Analog into the owner of every token authoring workflow or framework-specific theming API.

Tool How this design plays nicely with it Why the split matters
TokiForge Angular The pipeline can provide generated global token CSS, emitted file metadata, and framework bridge CSS while a TokiForge-style Angular runtime layer owns theme switching, signals, and app-level theme services. TokiForge proves that Angular-native DX often wants a runtime theme service on top of CSS variables. That should be an adapter layer, not Analog core.
Tokens Studio for Figma Tokens Studio can remain the design-source and sync layer, with tokens stored remotely or in Git, then transformed by Style Dictionary and served by the community pipeline. This is the cleanest upstream fit: design tokens flow from Figma to Git/JSON to Style Dictionary to app consumption without requiring a framework-owned token authoring model.
Panda CSS for Angular Panda can keep its own panda.config.ts, codegen, and PostCSS pipeline while consuming the same token source or emitted CSS-variable bridges from this pipeline. Panda is a compiler with its own config/codegen authority. The right integration is coexistence or adapter generation, not replacing Panda from a framework package.

Example integration shapes

  1. Tokens Studio -> Git sync -> Style Dictionary -> style pipeline -> Analog
    The pipeline watches exported token files, builds the outputs, injects the default CSS variables, and exposes bridge CSS for PrimeNG, Spartan, Web Awesome, MUI, or Tailwind consumers.

  2. TokiForge-style Angular runtime on top of pipeline outputs
    The pipeline owns token delivery; an Angular helper layer can own ThemeService, signals, theme persistence, and SSR-safe theme switching while components still consume the generated CSS variables.

  3. Panda alongside Analog
    Panda can still generate atomic classes and typed style APIs, while the pipeline provides shared token outputs or framework bridges for non-Panda consumers in the same monorepo.

The main design principle is consistent across all three: the pipeline should own token output lifecycle and delivery, while upstream token sources and downstream framework adapters keep ownership of their own semantics.

Initial Scope

Core

  • typed provider contract
  • typed output manifest contract
  • deterministic output ordering
  • watch file registration
  • virtual CSS/module support where needed
  • debug logging for pipeline events and generated outputs

Providers

  • Style Dictionary provider for token transforms and output generation
  • Panda provider for coexistence with Panda's config/codegen pipeline
  • Tokiforge provider for Angular-friendly runtime/theme integration patterns without pushing that into Analog core

Integration

  • Vite plugin orchestration layer
  • Analog usage examples and docs
  • examples for Tailwind-based consumption and framework bridge CSS

Non-Goals

  • no large new @analogjs/platform API
  • no provideDesignTokens() in Analog core
  • no Analog-owned 'primeng' | 'spartan' | 'daisyui' | ... target strings
  • no attempt to replace Tailwind, Panda, Tokiforge, or design-system-specific runtime APIs
  • no assumption that Analog must merge code to support this well

What Analog May Eventually Need

If community usage exposes real gaps, the eventual Analog ask should stay small and generic. Example areas:

  • stable stylesheet resource identity hooks
  • style pipeline registration/composition hooks
  • HMR invalidation helpers for generated style resources
  • documentation recipes for community plugins

That should only happen after real usage proves the need.

Package Direction In This Repo

The benevolent-dx ecosystem is the right home for the package family behind this work. Analog-facing packages in this repo should live under benevolent-dx/packages/analog-*.

Likely package direction:

  • benevolent-dx/packages/analog-style-pipeline-core
  • benevolent-dx/packages/analog-style-pipeline-vite
  • benevolent-dx/packages/analog-style-pipeline-style-dictionary-provider
  • benevolent-dx/packages/analog-style-pipeline-panda-provider
  • benevolent-dx/packages/analog-style-pipeline-tokiforge-provider

Phased Plan

  1. Tighten the generic provider/output contract
  2. Harden the Vite plugin around watch/build/HMR behavior
  3. Make Style Dictionary the first production-grade provider
  4. Add Panda coexistence support
  5. Add Tokiforge-oriented Angular runtime integration examples
  6. Publish docs and real Analog examples
  7. Re-evaluate whether Analog core needs any tiny generic extension point

Related Links

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions