// csstahak/ · 09 z 10

CSS Proměnné & Funkce

--custom-property, var(), calc(), min(), max(), clamp() — základ moderního design systému. Plus dark mode a @layer cascade.

--custom-property & var()

CSS proměnné

Definujte jednou, použijte všude. Dědí se, mění za runtime, fungují v media queries. Základ design systému.

MODERNÍ

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.

// var() fallbackvar(--barva, #2563EB) — pokud --barva není definována, použije se #2563EB. Fallback může být i další var(): var(--primary, var(--blue, #2563EB)). Řetězení fallbacků pro robustní systémy.
Definice a dědičnost
: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 */
Klíčová místa pro definici
:root — globální .component — lokální @media — responsive :hover — dynamické
  • :root = html element (nejvyšší úroveň)
  • Dědí se dolů DOM stromem
  • JS: el.style.setProperty('--color', 'red')
  • JS: getComputedStyle(el).getPropertyValue('--color')
  • Název je case-sensitive: --Color ≠ --color
Kompletní design token systém
CSS
: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');
Dark mode s CSS proměnnými

Tmavý motiv

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.

// color-scheme vlastnostcolor-scheme: light dark; říká prohlížeči aby přizpůsobil nativní UI elementy (scrollbary, formuláře, dialogy) aktuálnímu motivu. Nastavte na :root — jinak budou nativní prvky vypadat nesprávně v dark mode.
Živá ukázka
Světlý motiv

Obsah stránky v světlém motivu. Barvy jsou definovány přes CSS proměnné.

Tlačítko
Tmavý motiv

Stejný HTML, jiné hodnoty CSS proměnných v dark media query.

Tlačítko
  • color-scheme: light dark na :root
  • Proměnné přepsat jen v dark — vše ostatní zdědí
  • data-theme="dark" pro JS přepínač
  • localStorage pro uložení preference uživatele
  • prefers-color-scheme: system preference uživatele
Příklady
CSS + JS
/* 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;
calc() min() max() clamp()

CSS matematické funkce

Výpočty přímo v CSS. clamp() = fluid hodnoty bez media queries. Míchání jednotek.

MODERNÍ

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() pro fluid všeclamp(1rem, 2.5vw, 1.5rem) = minimálně 1rem, ideálně 2.5vw viewportu, maximálně 1.5rem. Automaticky škáluje mezi min a max hodnotou dle šířky viewportu. Žádné media queries! Funguje pro font-size, padding, margin, width, gap...
clamp() syntaxe
clamp(minimum, preferovaná, maximum)
clamp(1rem, 2.5vw, 1.5rem)
   ↑ min      ↑ fluid    ↑ max
Živá ukázka — clamp nadpis (zužte okno)
Fluid nadpis s clamp()
  • calc() míchá jednotky: calc(100% - 2rem)
  • min(90%, 1200px) = max-width zkratka
  • max(1rem, 2vw) = nejméně 1rem
  • clamp = min + preferred + max v jednom
  • Vnořené: clamp(1rem, calc(1vw + 0.5rem), 2rem)
Příklady
CSS
/* 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);
}
@layer

Cascade layers

Definujte pořadí CSS vrstev. Styly v nižší vrstvě VŽDY přebijí vyšší bez !important — revoluční kontrola nad kaskádou.

CSS 2022

@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.

// Proč @layer existujeCSS specificity problém: přidal jste knihovnu (Bootstrap, Tailwind) a teď musíte !important pro přepsání. @layer řeší: definujte pořadí vrstev předem — base → components → utilities. Vaše utility třídy vždy přebijí komponentní styly bez !important.
Pořadí vrstev (pozdější = vyšší priorita)
@layer base, components, utilities;
/* base < components < utilities */
/* utilities vždy vyhrají nad components */
Unlayered styly

Styly bez @layer mají vyšší prioritu než jakákoliv vrstva — jsou "nad" všemi vrstvami.

  • Deklarujte pořadí vrstev na začátku CSS
  • Unlayered styly > layered styly (bez ohledu na specificitu)
  • @layer utilities { .mt-4 { margin-top: 1rem; } }
  • Vnořené vrstvy: @layer base { @layer reset { } }
  • @import s vrstvou: @import "reset.css" layer(reset)
Příklady
CSS
/* 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 */
@layer reset, base, components, utilities;
Pořadí definovat jako první řádek v CSS
@container

Container queries

Stylování podle šířky rodiče, ne viewportu. Revoluce pro komponentní design. (Podrobně v 10. části.)

CSS 2023

@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.

// Proč container queries mění všeBez nich: .card musí znát kde v layoutu je (sidebar? main content?). S nimi: .card reaguje na svůj kontejner — je úzký? Zobrazí se jinak. Je široký? Jiný layout. Bez úprav na místě použití.
  • container-type: inline-size na rodiči
  • @container (min-width: 400px) { } na dítěti
  • container-name pro pojmenované kontejnery
  • Podpora: Chrome 105+, Firefox 110+, Safari 16+
Rychlá ukázka
CSS
/* 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 → */
// Copy-paste: kompletní design token základ s dark mode
CSS — production-ready design systém
/* === 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;
}
← 08 Pseudo 09 / 10 — CSS Proměnné & Funkce 10 Responzivita →