CivicDataLab / CivicDataLab/IDS-DRR-Frontend

Allow users to customize styles via CSS variables

Open
#438 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

enhancement
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.json declares ~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…/50 etc.). So the rgb-channel form for CSS vars is not required.
  • tailwind.config.js has zero plugins and no theme.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 in globals.css).
  • Fonts: next/font/google Inter loaded statically in app/[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 the build:tokens and postbuild:tokens scripts; drop the style-dictionary and fs-extra devDependencies 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.ts and config.ts — declare the deployment's font (pick a next/font/google family) 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 dev against the stub: the home, analytics, and datasets pages look identical to before the refactor (regression check).
  • npm run dev against the India branding (with a font override and at least one cssVars override) → font and the chosen accent colour change as expected.
  • npm run build succeeds without build: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/font requires 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?: NextFont and cssVars?: Record<string, string> to DeploymentConfig.
  • Inject cssVars and the font in layout.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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.