Overview of 999: Writing Maintainable CSS
Wes Bos and Scott Tolinski discuss what makes CSS maintainable over time and how to avoid the “rotting CSS” problem where styles leak, duplicate, and become impossible to safely change. The episode compares major modern CSS strategies—utility CSS, atomic CSS, CSS-in-JS, component-scoped CSS, CSS modules, BEM, and native CSS features like variables, layers, and scoping—while emphasizing that the best system is the one you can apply consistently. The core theme is simple: build reusable, flexible, and enforceable styling systems that adapt to content and layout changes without requiring endless overrides.
What Makes CSS Maintainable
Signs of healthy CSS
- Styles are isolated enough that they don’t leak across the app.
- Components can be moved out and still look correct on a blank page.
- Styles are reusable, not one-off fixes for a single screen.
- The system is easy to update without hunting through many files and breakpoints.
- The design language remains cohesive instead of drifting over time.
Common causes of CSS rot
- Overly broad selectors that affect unrelated parts of the site
- Too many hard-coded values instead of shared variables
- Repeated values for colors, shadows, spacing, and fonts
- Too many breakpoint-specific rules that re-implement the same styles
- Hacks and exceptions added when the system is too rigid or too limited
Core Principles for Good CSS
Build for flexibility
- CSS should adapt to different content, container sizes, and breakpoints.
- Use tools like:
clamp()for fluid typography- Grid utilities like
auto-fit/auto-fill - Flexbox for natural wrapping
- Container queries where appropriate
- Prefer systems where changing one value updates many related styles automatically.
Use variables aggressively
- The hosts strongly recommend using CSS custom properties for:
- font sizes
- colors
- shadows
- spacing
- border radii
- Variables help keep themes consistent and make global updates much safer.
- Relative/derived values are especially powerful:
- one base color can generate tints, borders, shadows, and overlays
- one spacing variable can drive multiple layout components
Favor reusable “global” solutions
- “Global” in this episode means shared, reusable building blocks, not necessarily a giant global stylesheet.
- Good examples:
- a reusable table component/style
- shared typography scale
- base form element styling
- design tokens and theme variables
- A good base should make plain HTML look decent before component styling is added.
CSS Approaches Compared
Utility CSS / Tailwind
- Styles are applied through small utility classes on the element.
- Pros:
- good scoping by default
- consistent system
- fast to build with
- reduced chance of leakage
- Cons:
- can become class-heavy and hard to read
- reuse often shifts from CSS selectors to copied class strings or wrapper components
- The hosts see it as useful, but not their favorite for everything.
Atomic CSS / Uno CSS
- Very similar to utility CSS: one class, one job.
- Uno CSS is highlighted as more customizable and able to feel like “your own Tailwind.”
- Good for teams that want atomic styling but with more control over the generated utilities.
StyleX
- A build-time CSS-in-JS approach used heavily at Meta.
- Styles are authored in JavaScript objects and compiled into optimized generated classes.
- Pros:
- avoids shipping repeated CSS
- very efficient at scale
- Cons:
- awkward syntax for many developers
- especially clunky for media queries
- The hosts are not fans of the ergonomics.
Panda CSS
- Similar space to CSS-in-JS, but more flexible and build-time focused.
- Can be used with CSS modules or inline styles.
- Seen as a nicer middle ground than StyleX for some workflows.
Component-scoped CSS
- A favorite of both hosts.
- Examples include:
- Vue scoped styles
- Svelte component styles
- CSS-in-JS scoped to a component
- Pros:
- CSS stays near the component
- less naming overhead
- styles don’t leak
- easy to reason about
- Tradeoff:
- harder to style nested child components without special handling
- sometimes requires props or global escape hatches
CSS Modules
- Styles live in separate
.cssfiles and are imported into components. - Pros:
- scoped class names
- familiar CSS syntax
- good separation of concerns
- Cons:
- separate files instead of co-located styles
- less convenient for deeply nested component targeting
- Wes says he’s largely happy with CSS Modules, but prefers co-located styles when possible.
BEM / SMACSS / OOCSS
- Older methodologies that were crucial before modern component systems existed.
- BEM in particular solved naming and structure problems in large CSS codebases.
- The hosts acknowledge it was very useful then, but it’s less necessary now with modern scoping and components.
Modern CSS Features Worth Using
CSS custom properties
- Live, cascade-aware, and reactive to parent overrides.
- Better than preprocessor variables because they update in the browser.
- Great for theme systems and relative styling.
@property
- Lets you define the type and behavior of a custom property.
- Useful for:
- animation
- type enforcement
- controlling inheritance
CSS layers
- Useful for explicitly controlling cascade order.
- Great for organizing:
- reset/base
- defaults
- component styles
- overrides/customizations
- Helps avoid specificity wars.
- Note:
!importantstill overrides layers.
CSS scoping
- Native CSS scoping is now available and lets styles be limited to a selector or closest parent scope.
- Can be used with a
<style>tag inside the component structure. - Includes “donut scope” behavior, where you can define both where a scope starts and where it stops.
- Seen as a big step toward native component-level styling.
Practical Tips and Recommendations
Do
- Pick one styling approach and stick with it as much as possible.
- Use variables for values that should stay in sync.
- Build styles that are responsive by design, not breakpoint-by-breakpoint replicas.
- Keep a solid global base:
- reset/normalize
- typography defaults
- theme variables
- form defaults
- Use layers and scoping to reduce specificity battles.
Don’t
- Hard-code many near-duplicate colors, shadows, or spacing values.
- Rebuild the same selectors at every breakpoint.
- Mix multiple CSS systems without a clear reason.
- Use hacks like overly specific selectors unless absolutely necessary.
- Rely on
!importantas a normal workflow.
Tooling and Enforcement
Stylelint
- Recommended as a guardrail for CSS consistency.
- Can enforce rules like:
- “don’t use raw color values; use variables”
- organization and naming constraints
- Helpful on teams because it removes the need for humans to police every PR.
Biome and ESLint plugins
- The hosts mention that CSS linting can also be integrated through other toolchains.
- The specific stack matters less than having some automated enforcement.
Main Takeaways
- Maintainable CSS is less about the “perfect” methodology and more about consistency, reuse, and flexibility.
- Variables and relative systems are key to keeping styles cohesive.
- Modern CSS features like layers, scoping, and
@propertymake it much easier to write clean systems. - Utility-first, scoped CSS, modules, and CSS-in-JS all have tradeoffs; pick the one that best fits your team and project.
- If your CSS starts to feel fragile, repetitive, or full of special cases, that’s a sign your system needs stronger structure or better guardrails.
