CivicDataLab / CivicDataLab/IDS-DRR-Frontend
Allow users to customize styles via CSS variables
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 0
- Forks
- 1
- Avg merge
- 6d 1h
- Merged PRs (30d)
- 1
Description
I suspect that any reuser will want to change the look-and-feel of the platform, at least via CSS.
I had Claude prepare this prompt, but I don't think I'll have time to review and iterate on it, so sharing here.
Refactor: replace Style Dictionary with shadcn-canonical CSS-var theming
Why
The current design-tokens pipeline (Style Dictionary → generated _variables.css + Tailwind color.js) is over-provisioned and harder to override per deployment than it needs to be. Refactoring to a CSS-variable-first setup makes per-deployment color overrides trivial AND aligns the codebase with the dominant Next.js + Tailwind v3 pattern.
This plan also folds in a configurable-font change (branding ships a next/font instance), since both touch layout.tsx and the DeploymentConfig contract.
Current state (audit summary)
config/tokens/tokens.jsondeclares ~274 color tokens. Only ~13 unique base-color Tailwind classes are referenced anywhere in the app (bg-baseIndigoSolid1,border-baseGraySlateSolid4,text-baseGraySlateSolid11, etc.). ≥95% of generated tokens are dead weight.- Zero usages of Tailwind opacity utilities with design tokens (no
bg-base…/50etc.). So the rgb-channel form for CSS vars is not required. tailwind.config.jshas zero plugins and notheme.extend— it replaces Tailwind's defaults wholesale with Style Dictionary outputs.- opub-ui consumes ~13 semantic CSS vars from
styles/tokens/_variables.css(--text-default,--text-interactive,--border-highlight-default,--icon-highlight,--background-solid-dark,--text-onbg-default, plus space/radius/font-size/z-index/outline). No collisions; opub-ui is a pure consumer. - Direct
var(--…)consumption in app code is essentially zero (2 sites inglobals.css). - Fonts:
next/font/googleInter loaded statically inapp/[locale]/layout.tsx:18.
Target state
Theming source of truth is CSS variables in a single hand-authored file. Tailwind references those CSS vars. Branding overrides = inject CSS vars + provide a next/font instance.
styles/theme.css ← :root CSS vars (the only source of truth)
tailwind.config.js ← references the CSS vars in theme.extend
app/[locale]/layout.tsx ← injects branding cssVars; uses branding font
No Style Dictionary, no generated JS color file, no build:tokens script.
Steps
1. Inventory actually-used Tailwind token classes
Grep the app for all token-derived class usage and build the canonical list (~13 names per audit). Capture both the class name AND the hex value it currently resolves to (from styles/tokens/tailwind/color.js).
grep -roE '(bg|text|border|fill|stroke|outline|ring)-base[A-Z][A-Za-z0-9]+' \
app components | sort -u
Add any semantic-token class usage (e.g. bg-backgroundSolidDark, text-textDefault) found in the same grep.
2. Author styles/theme.css
New file. Lift only the CSS vars actually referenced (by Tailwind classes, by opub-ui, and by globals.css). Use the same semantic names already in _variables.css so opub-ui keeps working. Approx size: ~50 vars (down from 112).
Keep the existing format (hex values, plain var(--name)); no need to refactor to rgb channels since no opacity utilities use these tokens.
3. Rewrite tailwind.config.js
Replace the imports from styles/tokens/tailwind/* with hand-written theme.extend.colors (and any other categories) referencing CSS vars:
theme: {
extend: {
colors: {
textDefault: 'var(--text-default)',
backgroundSolidDark: 'var(--background-solid-dark)',
iconHighlight: 'var(--icon-highlight)',
// ... only the names actually used
},
fontFamily: {
sans: 'var(--font-family-primary)',
},
},
},
Drop the space, borderRadius, borderWidth, fontSize, lineHeight, boxShadow, zIndex, transitionTimingFunction, transitionDuration, fontWeight overrides unless they're actually customised vs. Tailwind defaults — for any that are, keep them as var(--…) references and add the corresponding entries to theme.css.
Keep corePlugins.preflight = false (still required to avoid clashing with opub-ui's reset).
4. Update styles/globals.css
Replace @import './tokens/_variables.css'; with @import './theme.css';. Leave the opub-ui and nprogress imports as-is.
5. Delete obsolete machinery
config/tokens/(entire directory: tokens.json, sd-config.js, generate.mjs, copy.js, anything else)styles/tokens/(entire directory: _variables.css, tailwind/*)package.json: drop thebuild:tokensandpostbuild:tokensscripts; drop thestyle-dictionaryandfs-extradevDependencies if they're only used by the tokens pipeline.
6. Extend the branding contract
In ids-drr-branding-types/src/index.ts, add to DeploymentConfig:
import type { NextFont } from 'next/font';
export type DeploymentConfig = {
// ...existing fields
// Per-deployment font (e.g. a `next/font/google` or `/local` instance).
// Falls back to the frontend's default Inter when undefined.
font?: NextFont;
// CSS variable overrides applied at runtime to <html style>. Keys match
// the names in styles/theme.css (e.g. "--text-default").
cssVars?: Record<string, string>;
};
7. Update branding implementations
branding-stub/src/index.ts— no changes needed (defaults to undefined, which is fine).ids-drr-india-branding/src/index.tsandconfig.ts— declare the deployment's font (pick anext/font/googlefamily) and any CSS-var overrides.
8. Wire through config/site.ts
Add re-exports:
export const font = config.font;
export const cssVars: Record<string, string> = config.cssVars ?? {};
9. Apply in app/[locale]/layout.tsx
import { cssVars, font } from '@/config/site';
const fallbackFont = FontSans({ subsets: ['latin'], display: 'swap' });
const activeFont = font ?? fallbackFont;
<html lang={locale} style={cssVars as CSSProperties}>
...
<body className={activeFont.className}>
10. Verify
npx tsc --noEmit→ 0 errors.npm test→ 143/143 pass.npm run devagainst the stub: the home, analytics, and datasets pages look identical to before the refactor (regression check).npm run devagainst the India branding (with a font override and at least one cssVars override) → font and the chosen accent colour change as expected.npm run buildsucceeds withoutbuild:tokens(confirm script deletion didn't break the build chain).
Files touched
- New:
styles/theme.css - Modified:
tailwind.config.js,styles/globals.css,app/[locale]/layout.tsx,config/site.ts,branding-stub/src/index.ts,package.json - Modified (separate repos):
ids-drr-branding-types/src/index.ts,ids-drr-india-branding/src/{index.ts,config.ts} - Deleted:
config/tokens/**,styles/tokens/**
Effort & risks
Effort: ~1 day. Dominated by inventorying actually-used tokens, hand-authoring theme.css and tailwind.config.js, and visual smoke-testing.
Risks:
- A token in use via runtime-computed classnames may slip past the static grep. Mitigation: keep every var that opub-ui's CSS references (per audit, ~13 semantic vars), then expand if regressions appear.
- 84
baseIndigoAlpha1..12-style alpha tokens exist as separate hex values. None used per the audit, but spot-check before deleting. next/fontrequires statically-known font choices, which is fine because branding is a static TS module — but be aware that the font instance must be resolvable at module load time, not lazily.
Trade-off note
If the refactor scope feels too large for the current cycle, the cheap version is:
- Add
font?: NextFontandcssVars?: Record<string, string>toDeploymentConfig. - Inject
cssVarsand the font inlayout.tsx. - Leave Style Dictionary in place.
That gets per-deployment fonts and a few-color override in ~half a day, at the cost of keeping the over-provisioned tokens pipeline.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the token inventory, then read styles/globals.css, tailwind.config.js, app/[locale]/layout.tsx, and config/site.ts; review the branding type and India branding files in their separate repositories. Run the listed grep and inspect the existing token outputs before changing the theme. Done means CSS-variable overrides and branding fonts work, typecheck, tests, build, and both branding smoke checks pass.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- css, next.js, tailwindcss, typescript
- Domain
- build-system, design, frontend, web-dev
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 48/100