--custom-property, var(), calc(), min(), max(), clamp() — základ moderního design systému. Plus dark mode a @layer cascade.
Definujte jednou, použijte všude. Dědí se, mění za runtime, fungují v media queries. Základ design systému.
CSS custom properties (proměnné) se definují s -- prefixem. var(--nazev, fallback) je použije. Na rozdíl od SASS proměnných: dědí se v DOM, mění se za runtime (JS), fungují uvnitř media queries a :hover. Základ každého design systému.
:root { --color: blue; } /* globální */ .card { --color: red; } /* přepíše pro .card a jeho children */ p { color: var(--color); } /* blue nebo red dle kontextu */
:root {
/* === BARVY === */
--color-primary: #2563EB;
--color-primary-hover: #1d4ed8;
--color-secondary: #7c3aed;
--color-success: #16a34a;
--color-danger: #dc2626;
--color-warning: #d97706;
/* Neutrální */
--color-bg: #ffffff;
--color-surface: #f9fafb;
--color-border: #e5e7eb;
--color-text: #111827;
--color-text-muted: #6b7280;
/* === TYPOGRAFIE === */
--font-sans: system-ui, sans-serif;
--font-mono: monospace;
--text-sm: 0.875rem;
--text-base: 1rem;
--text-lg: 1.125rem;
--text-xl: 1.25rem;
--text-2xl: 1.5rem;
--text-3xl: 2rem;
--leading-tight: 1.2;
--leading-normal: 1.6;
/* === ROZESTUPY === */
--space-1: 0.25rem;
--space-2: 0.5rem;
--space-3: 0.75rem;
--space-4: 1rem;
--space-6: 1.5rem;
--space-8: 2rem;
/* === RADIUS === */
--radius-sm: 4px;
--radius: 8px;
--radius-lg: 12px;
--radius-full: 9999px;
/* === STÍNY === */
--shadow-sm: 0 1px 3px rgba(0,0,0,0.1);
--shadow: 0 4px 12px rgba(0,0,0,0.12);
--shadow-lg: 0 8px 32px rgba(0,0,0,0.15);
}
/* Použití */
.btn {
background: var(--color-primary);
padding: var(--space-2) var(--space-4);
border-radius: var(--radius);
font-size: var(--text-base);
box-shadow: var(--shadow-sm);
}
.btn:hover {
background: var(--color-primary-hover);
}
/* Komponentní proměnné */
.card {
--card-padding: var(--space-6);
--card-radius: var(--radius-lg);
padding: var(--card-padding);
border-radius: var(--card-radius);
}
.card-compact { --card-padding: var(--space-3); }
/* JS dynamická změna */
document.documentElement
.style.setProperty('--color-primary', '#dc2626');
prefers-color-scheme + CSS proměnné = automatický dark mode. Data-theme atribut pro manuální přepínač.
Kombinace CSS proměnných + @media (prefers-color-scheme: dark) = automatický dark mode bez duplikace CSS pravidel. Přepište jen hodnoty proměnných — vše ostatní se přizpůsobí automaticky. Pro manuální přepínač: data-theme atribut na html.
Obsah stránky v světlém motivu. Barvy jsou definovány přes CSS proměnné.
Stejný HTML, jiné hodnoty CSS proměnných v dark media query.
/* Základní přístup */
:root {
color-scheme: light dark;
--bg: #ffffff;
--text: #111827;
--surface: #f9fafb;
--border: #e5e7eb;
--primary: #2563EB;
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #0a0a0f;
--text: #f3f4f6;
--surface: #1a1a2e;
--border: #2a2a3e;
--primary: #60a5fa;
}
}
body {
background: var(--bg);
color: var(--text);
}
/* === Manuální přepínač (data-theme) === */
/* Světlý motiv (výchozí) */
:root, [data-theme="light"] {
--bg: #ffffff;
--text: #111827;
/* ... */
}
/* Tmavý motiv */
[data-theme="dark"] {
--bg: #0a0a0f;
--text: #f3f4f6;
/* ... */
}
/* Respektovat systémovou preferenci pokud není nastaveno */
@media (prefers-color-scheme: dark) {
:root:not([data-theme]) {
--bg: #0a0a0f;
--text: #f3f4f6;
}
}
/* === JS přepínač === */
const btn = document.getElementById('theme-btn');
const html = document.documentElement;
btn.addEventListener('click', () => {
const current = html.dataset.theme;
const next = current === 'dark' ? 'light' : 'dark';
html.dataset.theme = next;
localStorage.setItem('theme', next);
});
// Inicializace
const saved = localStorage.getItem('theme');
if (saved) html.dataset.theme = saved;
Výpočty přímo v CSS. clamp() = fluid hodnoty bez media queries. Míchání jednotek.
calc() = matematické výrazy, míchá jednotky. min(a, b) = menší z hodnot. max(a, b) = větší z hodnot. clamp(min, preferred, max) = hodnota v rozmezí — základ fluid typografie a responzivního layoutu bez media queries.
clamp(minimum, preferovaná, maximum) clamp(1rem, 2.5vw, 1.5rem) ↑ min ↑ fluid ↑ max
/* calc() — míchání jednotek */
.sidebar {
width: calc(100% - 280px);
/* 100% šířka minus sidebar */
}
.full-bleed {
width: calc(100% + 2rem);
margin-left: -1rem;
/* Přesáhne přes padding rodiče */
}
.responsive-padding {
padding: calc(1rem + 1vw);
/* Roste s viewportem */
}
/* min() — max-width zkratka */
.container {
width: min(90%, 1200px);
/* = max-width: 1200px; width: 90%; */
margin: 0 auto;
}
/* max() — min-width efekt */
.btn {
width: max(200px, 50%);
/* Nejméně 200px, ideálně 50% */
}
/* clamp() — fluid typografie */
:root {
--text-sm: clamp(0.875rem, 1vw, 0.9375rem);
--text-base: clamp(1rem, 1.5vw, 1.125rem);
--text-lg: clamp(1.125rem, 2vw, 1.25rem);
--text-xl: clamp(1.25rem, 2.5vw, 1.5rem);
--text-2xl: clamp(1.5rem, 4vw, 2rem);
--text-3xl: clamp(2rem, 5vw, 3rem);
--text-hero: clamp(2.5rem, 8vw, 6rem);
}
/* clamp() — fluid padding */
.section {
padding: clamp(2rem, 5vw, 6rem) 1rem;
}
/* clamp() — fluid gap */
.grid {
display: grid;
gap: clamp(1rem, 2vw, 2rem);
}
/* Vnořené funkce */
h1 {
font-size: clamp(
1.75rem,
calc(1rem + 3vw),
4rem
);
}
/* round(), mod(), rem() — nové v CSS */
.grid-item {
width: round(down, 100% / 3, 1px);
}
Definujte pořadí CSS vrstev. Styly v nižší vrstvě VŽDY přebijí vyšší bez !important — revoluční kontrola nad kaskádou.
@layer vytvoří pojmenovanou vrstvu (layer) v CSS kaskádě. Styly v pozdějších vrstvách přebijí dřívější bez ohledu na specificitu. Nevrstvené styly mají vždy nejvyšší prioritu. Řeší problém !important a specificita válek.
@layer base, components, utilities; /* base < components < utilities */ /* utilities vždy vyhrají nad components */
Styly bez @layer mají vyšší prioritu než jakákoliv vrstva — jsou "nad" všemi vrstvami.
/* 1. Definice pořadí vrstev */
@layer reset, base, components, utilities;
/* 2. Naplnění vrstev */
@layer reset {
*, *::before, *::after {
box-sizing: border-box;
margin: 0; padding: 0;
}
}
@layer base {
body {
font-family: system-ui, sans-serif;
line-height: 1.6;
}
a { color: #2563EB; }
}
@layer components {
.btn {
padding: 0.5rem 1rem;
background: #2563EB;
color: #fff;
border-radius: 6px;
}
/* Specificita: 0,1,0 */
}
@layer utilities {
.mt-4 { margin-top: 1rem; }
.text-red { color: #dc2626 !important; }
/* Specificita: 0,1,0 ale utilities > components */
}
/* Unlayered = nejvyšší priorita */
.special { background: gold; }
/* Přebije i utilities vrstvu! */
/* Import s vrstvou */
@import url("bootstrap.css") layer(bootstrap);
/* Bootstrap je nyní v bootstrap vrstvě */
/* Vaše styly ji přebijí bez !important */
/* Vnořené vrstvy */
@layer framework {
@layer base { /* ... */ }
@layer theme { /* ... */ }
}
/* framework.base < framework.theme */
Stylování podle šířky rodiče, ne viewportu. Revoluce pro komponentní design. (Podrobně v 10. části.)
@container = media queries pro kontejner místo viewportu. Komponent reaguje na šířku svého rodiče — ne celého okna. Základ skutečně znovupoužitelných komponent. Podrobně rozepsáno v sekci 10 Responzivita.
/* 1. Definovat kontejner na rodiči */
.card-wrapper {
container-type: inline-size;
container-name: card; /* volitelné */
}
/* 2. Stylovat child dle kontejneru */
.card {
display: flex;
flex-direction: column;
}
@container card (min-width: 400px) {
.card {
flex-direction: row;
}
.card-img { width: 40%; }
}
/* Bez container-name */
@container (min-width: 600px) {
.grid {
grid-template-columns: repeat(3, 1fr);
}
}
/* Podrobně v 10-responsivita.html → */
/* === DESIGN TOKENS — světlý motiv === */
:root {
color-scheme: light dark;
/* Primární barvy */
--primary-50: #eff6ff;
--primary-100: #dbeafe;
--primary-500: #3b82f6;
--primary-600: #2563eb;
--primary-700: #1d4ed8;
/* Neutrální */
--neutral-50: #f9fafb;
--neutral-100: #f3f4f6;
--neutral-200: #e5e7eb;
--neutral-400: #9ca3af;
--neutral-600: #4b5563;
--neutral-900: #111827;
/* Sémantické tokeny (light) */
--color-bg: var(--neutral-50);
--color-surface: #ffffff;
--color-border: var(--neutral-200);
--color-text: var(--neutral-900);
--color-muted: var(--neutral-600);
--color-primary: var(--primary-600);
--color-primary-hover: var(--primary-700);
/* Typografie */
--font-sans: system-ui, -apple-system, sans-serif;
--font-mono: 'JetBrains Mono', monospace;
/* Fluid text scale */
--text-xs: clamp(0.75rem, 0.8vw, 0.8125rem);
--text-sm: clamp(0.875rem, 1vw, 0.9375rem);
--text-base: clamp(1rem, 1.2vw, 1.0625rem);
--text-lg: clamp(1.125rem, 1.5vw, 1.25rem);
--text-xl: clamp(1.25rem, 2vw, 1.5rem);
--text-2xl: clamp(1.5rem, 3vw, 2rem);
--text-3xl: clamp(2rem, 5vw, 3rem);
/* Rozestupy */
--space-1: 0.25rem; --space-2: 0.5rem;
--space-3: 0.75rem; --space-4: 1rem;
--space-5: 1.25rem; --space-6: 1.5rem;
--space-8: 2rem; --space-12: 3rem;
--space-16: 4rem;
/* Radius */
--radius-sm: 4px;
--radius: 8px;
--radius-lg: 12px;
--radius-xl: 16px;
--radius-full: 9999px;
/* Stíny */
--shadow-sm: 0 1px 2px rgba(0,0,0,0.05);
--shadow: 0 1px 3px rgba(0,0,0,0.1),
0 1px 2px rgba(0,0,0,0.06);
--shadow-lg: 0 10px 15px rgba(0,0,0,0.1),
0 4px 6px rgba(0,0,0,0.05);
/* Tranzice */
--transition-fast: 150ms ease;
--transition: 250ms ease;
--transition-slow: 400ms ease;
/* Z-index */
--z-dropdown: 10;
--z-sticky: 20;
--z-modal: 40;
--z-toast: 50;
}
/* === DARK MODE === */
@media (prefers-color-scheme: dark) {
:root {
--color-bg: #0a0a0f;
--color-surface: #1a1a2e;
--color-border: #2a2a3e;
--color-text: #f3f4f6;
--color-muted: #9ca3af;
--color-primary: var(--primary-500);
--color-primary-hover: var(--primary-600);
}
}
[data-theme="dark"] {
--color-bg: #0a0a0f;
--color-surface: #1a1a2e;
--color-border: #2a2a3e;
--color-text: #f3f4f6;
--color-muted: #9ca3af;
--color-primary: var(--primary-500);
}
/* === BASE STYLY === */
*, *::before, *::after { box-sizing: border-box; }
body {
font-family: var(--font-sans);
font-size: var(--text-base);
line-height: 1.6;
background: var(--color-bg);
color: var(--color-text);
-webkit-font-smoothing: antialiased;
}