-
Notifications
You must be signed in to change notification settings - Fork 2
ADR 006 Styling Approach
Accepted (Revised -- originally Tailwind CSS v4, changed to CSS Modules)
Cornerstone needs a CSS approach that supports:
- Responsive design (desktop, tablet, mobile-friendly)
- Consistent design system (colors, spacing, typography)
- Interactive components (Gantt chart, drag-and-drop, modals, forms)
- Touch-friendly interfaces for mobile/tablet
- Fast development iteration
- Small production bundle size
Per ADR-004, the frontend build chain must avoid native binary dependencies. This eliminates tools that rely on native code for CSS processing.
Tailwind CSS v4 (original decision)
- Utility-first CSS framework with zero runtime
- Excellent responsive design utilities
- Design system built in
- v4 uses an oxide engine that ships native binaries
- Rejected: Tailwind CSS v4's oxide engine depends on Lightning CSS, which ships platform-specific native binaries that crash on ARM64 emulation. This is the same class of issue that eliminated Vite (ADR-004).
styled-components / Emotion (CSS-in-JS)
- Dynamic styles based on props
- Runtime CSS injection -- adds JavaScript overhead
- Larger bundle size due to runtime
- Good component encapsulation but slower development for layout work
- Not chosen: Runtime overhead and bundle size penalty are unnecessary for this project's scale.
CSS Modules
- Scoped CSS per component; no class name collisions
- Standard CSS syntax with module import (
import styles from './Foo.module.css') - Works with Webpack's css-loader (pure JavaScript, no native binaries)
- No runtime overhead -- compiled to unique class names at build time
- CSS custom properties (variables) provide a lightweight design system
- Full control over responsive design via standard media queries
- Well-supported in the React ecosystem
Use CSS Modules for component-scoped styling, processed by Webpack's css-loader.
Each component has a co-located .module.css file:
components/
Sidebar/
Sidebar.tsx
Sidebar.module.css
CSS classes are imported as a typed object:
import styles from './Sidebar.module.css';
function Sidebar() {
return <nav className={styles.sidebar}>...</nav>;
}Webpack's css-loader transforms class names to unique identifiers (e.g., Sidebar_sidebar_a1b2c) at build time, preventing collisions.
CSS custom properties (variables) defined in a global stylesheet (client/src/styles/index.css) provide the design system:
:root {
--color-primary: #2563eb;
--color-surface: #ffffff;
--spacing-sm: 0.5rem;
--spacing-md: 1rem;
/* etc. */
}Components reference these variables, ensuring visual consistency without a framework dependency.
Standard CSS media queries within each module handle responsive layouts:
.sidebar {
width: 280px;
}
@media (max-width: 768px) {
.sidebar {
transform: translateX(-100%);
}
}A type declaration file (client/src/types/css.d.ts) provides typing for CSS Module imports:
declare module '*.module.css' {
const classes: { readonly [key: string]: string };
export default classes;
}In Jest tests, identity-obj-proxy mocks CSS Module imports, returning class names as strings (e.g., styles.sidebar returns "sidebar").
- Pure JavaScript build chain -- no native binary dependencies
- Zero runtime overhead (styles are compiled at build time)
- Component-scoped styles prevent class name collisions
- Standard CSS syntax -- no framework-specific learning curve
- Full control over responsive design, animations, and custom components (e.g., Gantt chart)
- CSS custom properties provide a lightweight, standards-based design system
- Works seamlessly with Webpack (ADR-004) and Jest (ADR-005)
- No built-in utility classes -- requires writing more CSS for common patterns (padding, margin, flex)
- Responsive styles require explicit media queries in each module (no shorthand prefixes like Tailwind's
md:) - No design system out of the box -- must define and maintain custom properties manually
- More CSS files to maintain compared to utility-first approaches