Modern CSS in 2026 — Tailwind v4, Open Props, and CUBE CSS Architecture
The landscape today
| Approach | Bundle size | DX | Learning curve | Fit |
|---|---|---|---|---|
| Utility-first (Tailwind v4) | Tiny with JIT | Excellent | Moderate | Product teams |
| Design tokens (Open Props) | ~5 KB | Good | Low | Designers who like pure CSS |
| Modular architecture (CUBE CSS) | Varies | Great long-term | High initially | Long-lived projects |
| CSS-in-JS (styled-components etc.) | Runtime cost | Good | Moderate | React-heavy SPAs |
| Vanilla CSS + modern features | Smallest | Depends | Low | Small sites |
No single answer. Small marketing site? Vanilla CSS. Product UI with a team? Tailwind or CUBE. Design system? Open Props.
Tailwind v4 — what's new
Released 2024, production-ready 2026:
- Instant build — Rust-powered compiler, 5-10× faster than v3
- Zero config for most projects — no
tailwind.config.jsneeded, works with@import "tailwindcss" - CSS-first configuration — customise in CSS, not JS config file
- Container queries built in
- Auto content detection — scans your HTML/JS/TS without the
content:array - Native CSS cascade layers used for predictable specificity
Setup (Vite project)
npm install tailwindcss @tailwindcss/vitevite.config.ts:
import tailwindcss from '@tailwindcss/vite';
export default {
plugins: [tailwindcss()],
};src/app.css:
@import "tailwindcss";
/* Customise inline — no separate config file */
@theme {
--color-brand-50: oklch(0.98 0.02 260);
--color-brand-500: oklch(0.6 0.2 260);
--color-brand-900: oklch(0.2 0.1 260);
--font-sans: "Inter", sans-serif;
--spacing: 0.25rem; /* default spacing unit */
}Use in HTML:
<button class="bg-brand-500 text-white px-6 py-3 rounded-lg hover:bg-brand-900">
Click
</button>v4 migration from v3
npx @tailwindcss/upgrade@nextAutomates 90% of changes. Manual fixes:
ringutilities now default to 1px (was 3px)bg-opacity-*→ usebg-black/50syntaxspace-y-4alternative:flex-col gap-4
Open Props — design tokens as CSS variables
Open Props is just a CSS file with 500+ variables. No build step, no framework, works anywhere.
npm install open-props
# or via CDN@import "open-props/style";
@import "open-props/normalize";
.card {
background: var(--surface-2);
padding: var(--size-fluid-3);
border-radius: var(--radius-3);
box-shadow: var(--shadow-3);
color: var(--text-1);
}
.button {
background: var(--blue-6);
color: white;
padding: var(--size-2) var(--size-4);
border-radius: var(--radius-2);
transition: background var(--ease-3) 150ms;
}
.button:hover {
background: var(--blue-7);
}Includes:
- Colors (2500+ hues, dark mode ready)
- Spacing (t-shirt sizes + fluid)
- Shadows
- Border radius
- Animations + easings
- Gradients
- Media queries (
var(--md)etc.)
No class pollution — just tokens. Pair with your own selectors or other frameworks.
CUBE CSS — architecture without framework
CUBE = Composition, Utility, Block, Exception. A methodology, not a library.
The four layers
- Composition — global layout rules (macro level)
- Utility — single-purpose classes for small adjustments
- Block — named components with their own styles
- Exception — rare one-off overrides via data-attributes
Example folder structure
styles/
├── composition/
│ ├── stack.css /* vertical rhythm */
│ ├── cluster.css /* horizontal groups */
│ ├── sidebar.css /* content + sidebar layout */
├── utilities/
│ ├── color.css
│ ├── text.css
│ ├── spacing.css
├── blocks/
│ ├── card.css
│ ├── nav.css
│ ├── form.css
├── exceptions/ /* data-state overrides */
├── tokens.css /* CSS custom properties */
└── main.css /* imports all */Stack (composition primitive)
.stack {
display: flex;
flex-direction: column;
justify-content: flex-start;
}
.stack > * + * {
margin-block-start: var(--space-m);
}<section class="stack">
<h1>Welcome</h1>
<p>Article content…</p>
<button>Read more</button>
</section>Every direct child gets consistent spacing. No per-component margin tweaks.
Block (component)
.card {
background: var(--surface-2);
padding: var(--space-m);
border-radius: var(--radius-3);
}
.card__title {
font-size: var(--font-size-3);
font-weight: 600;
}
.card__body {
color: var(--color-text-2);
}Exception (state overrides)
[data-state="open"] { display: block; }
[data-state="closed"] { display: none; }
[data-loading="true"] .card { opacity: 0.5; pointer-events: none; }Modern CSS features worth using in 2026
Container queries
.card-grid {
container-type: inline-size;
}
.card {
padding: var(--space-s);
}
@container (min-width: 600px) {
.card {
display: grid;
grid-template-columns: 150px 1fr;
padding: var(--space-m);
}
}Responsive based on parent size, not viewport. Killer for reusable components.
:has() — parent selection
form:has(:invalid) button[type="submit"] {
opacity: 0.5;
pointer-events: none;
}CSS nesting (native)
.card {
padding: var(--space-m);
& h2 {
font-size: var(--font-size-3);
}
&:hover {
transform: translateY(-2px);
}
}No preprocessor needed.
color-mix() and oklch
:root {
--brand: oklch(0.6 0.2 260);
--brand-lighter: color-mix(in oklch, var(--brand) 70%, white);
}oklch gives perceptually-uniform colors — no more "muddy" gradients.
@scope (limited support 2026)
@scope (.card) {
h2 { color: var(--brand); }
}Styles only apply inside the scope. Cleaner than BEM for component isolation.
Which to choose?
Migration tips
From Bootstrap → Tailwind
- Replace grid classes:
col-md-6→md:w-1/2 - Forms: use
@tailwindcss/formsplugin - Components: build your own; copy from ui.shadcn.com (free)
From CSS-in-JS → vanilla + tokens
- Extract colors, spacing, fonts to CSS variables
- Move component styles to CSS files
- Tree-shake styled-components/emotion packages
Common pitfalls
FAQ
For <10 pages with no team: vanilla CSS + Open Props is faster to write and understand. Tailwind shines when you have many pages or components to keep consistent.
Yes — it's a CSS tool, framework-agnostic. Configure the build step to watch your templates.
JIT purges to only classes you use. Typical production CSS is 15-30 KB gzipped. Smaller than Bootstrap.
Declining trend. Modern browser features (scoping, nesting, container queries) + vanilla CSS + tokens cover most cases without runtime cost.
color-scheme: light dark + prefers-color-scheme media query. Use CSS variables; flip values in @media (prefers-color-scheme: dark). Tailwind v4 supports dark: prefix automatically.
Modern CSS runs anywhere — ship your next site on DomainIndia. Pick a plan