How CSS Classes Are Scoped and Managed in the Instatic Editor
Instatic scopes CSS classes using a strict CSS Modules-only architecture where Vite hashes class names at build time to guarantee component isolation, while a centralized token system in src/styles/globals.css manages all visual values and is enforced by automated tests.
The Instatic editor, developed by CoreBunch using React 19, implements a rigorous styling system designed to eliminate global CSS pollution and prevent naming collisions. By mandating CSS Modules for all component-specific styles and prohibiting Tailwind utilities, the codebase ensures that CSS classes are scoped locally and managed through a design-token-driven approach.
Local Scoping via CSS Modules
Instatic enforces a CSS Modules-only policy as documented in docs/design.md and docs/editor.md. Every React component that requires custom styling must maintain a co-located Component.module.css file within the same directory as its TypeScript implementation.
Component-Level Module Files
When a developer imports a CSS Module using import styles from "./Component.module.css", the build pipeline processes the file to generate unique class names. For instance, a class defined as .button in src/ui/components/Button/Button.module.css is transformed at build time into a hashed identifier such as Button_button__xYz12, ensuring it is scoped exclusively to that component instance and cannot leak into the global namespace.
// src/ui/components/Button/Button.tsx
import { cn } from '@ui/cn' // tiny class‑name combiner
import styles from './Button.module.css' // CSS‑Modules import
type Props = {
/** optional extra classes */
className?: string
/** button label */
children: React.ReactNode
}
/** Simple button that follows the Instatic styling rules */
export const Button = ({ className, children }: Props) => (
<button className={cn(styles.button, className)}>
{children}
</button>
)
/* src/ui/components/Button/Button.module.css */
.button {
/* token‑based styling – never a hard‑coded color */
background: var(--accent-2);
color: var(--text-bright);
border-radius: var(--editor-radius);
padding: var(--space-xs) var(--space-m);
font-size: var(--text-s);
}
Vite Build Integration
The project uses Vite with Bun as its build tooling. Vite's native CSS Modules configuration automatically handles the generation of hashed class names and injects the resulting styles at runtime. Because the React Compiler auto-memoizes components, the imported styles object remains referentially stable across renders, eliminating the need for manual useMemo or useCallback hooks for style references.
Managing Visual Values with Design Tokens
All visual values—colors, spacing, typography, and radii—are defined as CSS custom properties in src/styles/globals.css. This file serves as the single source of truth for the entire application's visual design, ensuring consistent styling across src/ui/ and src/admin/ components.
Token-Based Styling
CSS modules must reference these tokens using var(--token-name) syntax rather than hard-coded hex codes, pixel values, or rem units. The globals.css file defines variables such as --text-s, --space-xs, --accent-2, and --editor-radius, which components consume to maintain visual consistency.
Automated Enforcement
Compliance with the token system is verified by the test suite in src/__tests__/architecture/css-token-policy.test.ts. This test ensures that no CSS module contains literal color values or arbitrary units, forcing all styling decisions through the centralized token catalog documented in docs/reference/design-tokens.md.
Class Name Composition
To safely combine locally scoped classes with external props, Instatic provides a lightweight utility in src/ui/cn.ts. This helper merges the CSS Module object with any className passed via props, allowing components to remain extensible while preserving their base styles.
Prohibited Patterns
The repository strictly bans Tailwind utilities and raw CSS imports outside of the designated folders. The only permitted global stylesheet is src/styles/globals.css; all other styles must reside in CSS modules under src/admin/, src/admin/pages/site/, or src/ui/. These constraints are validated by noTailwindUtilities.test.ts and the CSS token policy tests.
Summary
- CSS Modules-only: Every component uses co-located
*.module.cssfiles to ensure styles are scoped via build-time hashing. - Automatic scoping: Vite generates unique hashed class names (e.g.,
Button_button__xYz12) that prevent collision with other components. - Design tokens: All visual values are defined in
src/styles/globals.cssand referenced via CSS variables, enforced bycss-token-policy.test.ts. - Composition utility: The
cnhelper insrc/ui/cn.tssafely merges local styles with externalclassNameprops. - Strict prohibitions: Tailwind and arbitrary global CSS are banned, with compliance verified by the test suite.
Frequently Asked Questions
How does Instatic prevent CSS class name collisions?
Instatic relies on CSS Modules processed by Vite. When a component imports its associated .module.css file, the build system rewrites each class name to a unique hash derived from the component path and original class name. This guarantees that .button in one component becomes Button_button__xYz12 while .button in another becomes something like Card_button__aBc34, eliminating any risk of global namespace pollution.
Why doesn't Instatic use Tailwind CSS?
The project explicitly bans Tailwind utilities as documented in docs/design.md and enforced by noTailwindUtilities.test.ts. The maintainers opted for CSS Modules to ensure strict component isolation and to force all visual decisions through the centralized design token system in globals.css, preventing utility class proliferation and maintaining a single source of truth for spacing, color, and typography values.
How are design tokens enforced in CSS modules?
The test file src/__tests__/architecture/css-token-policy.test.ts scans all CSS modules for hard-coded values such as hex colors, pixel units, or rem values. If a module contains any literal values instead of var(--token-name) references, the test suite fails, ensuring that every visual property derives from the tokens defined in src/styles/globals.css.
What build tools handle the CSS module hashing?
Instatic uses Vite paired with Bun as its build pipeline. Vite's built-in CSS Modules configuration automatically handles the compilation, hashing, and injection of scoped class names without additional configuration. The React Compiler further optimizes runtime performance by ensuring the imported styles object remains referentially stable across component renders.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →