# 16 • CSS kaskáda

> CSS specificita, kaskáda, izolace, konvence (BEM)
> 

# Co je CSS

**CSS** *(Cascading Style Sheets)* je jazyk pro popis vzhledu webových stránek. HTML strukturuje obsah, CSS řeší jak ten obsah vypadá, JavaScript řeší chování.

### Tři pilíře webu

```
HTML  → struktura a sémantika
**CSS   → vzhled (barvy, layout, animace)**
JS    → chování a interaktivita
```

# Co je CSS kaskáda

**Kaskáda** je mechanismus, který rozhoduje, **které CSS pravidlo vyhraje**, když více pravidel cílí na stejný element a stejnou vlastnost.

Vyhodnocuje se v tomto pořadí:

1. **Původ a důležitost** (odkud styl pochází a jestli má `!important`)
2. **Specificita** (jak přesný je selektor)
3. **Pořadí** (které pravidlo je v kódu pozdější)

Kromě toho existuje ještě **dědičnost**, což je odlišný mechanismus, jak hodnoty přechází z rodiče na potomka.

## Původ stylů (origin)

Styly můžou pocházet ze tří zdrojů. Společně s `!important` se vyhodnocují v této hierarchii:

| Priorita (od nejnižší k nejvyšší) | Kategorie |
| --- | --- |
| 1 | Normální styly **prohlížeče** (user-agent) |
| 2 | Normální styly **uživatele** (extensions, accessibility) |
| 3 | Normální styly **autora** (tvoje CSS) |
| 4 | CSS **animace** (`@keyframes` během animace) |
| 5 | `!important` styly **autora** |
| 6 | `!important` styly **uživatele** |
| 7 | `!important` styly **prohlížeče** |
| 8 | CSS **transitions** (přechody) |

> 
> 
> 
> Druhá věc: **animace a transitions** mají vlastní úroveň, transitions dokonce přebíjí i `!important`. Proto blikne button při hoveru, i když máš `color: red !important`.
> 

# Specificita

Specificita určuje, který selektor je "přesnější", tedy má vyšší prioritu. Počítá se jako čtyřmístné číslo `(inline, ID, třída, tag)`.

| Selektor | Specificita | Příklad |
| --- | --- | --- |
| Inline styl | **1,0,0,0** | `style="color: red"` |
| ID selektor | **0,1,0,0** | `#header { }` |
| Třída, pseudo-třída, atribut | **0,0,1,0** | `.button`, `:hover`, `[type]` |
| Element, pseudo-element | **0,0,0,1** | `p { }`, `::before` |
| Univerzální `*` | **0,0,0,0** | `* { }` |
| `:not()`, `:is()`, `:has()` | dle nejvyšší specificity argumentu | viz níže |
| `:where()` | **0,0,0,0** (nulová) | `:where(.btn)` |

### Příklady výpočtu

```css
h1                  { }   /* 0,0,0,1 */
.title              { }   /* 0,0,1,0 */
#header             { }   /* 0,1,0,0 */
div p               { }   /* 0,0,0,2 (dva elementy) */
.card .title        { }   /* 0,0,2,0 (dvě třídy) */
#nav .item:hover    { }   /* 0,1,2,0 (id + třída + pseudo-třída) */
a[href^="https"]    { }   /* 0,0,1,1 (atribut + element) */
```

### Konkrétní souboj

```css
h1       { color: blue; }    /* 0,0,0,1 */
.title   { color: red; }     /* 0,0,1,0 */
#header  { color: green; }   /* 0,1,0,0 */
```

Element `<h1 id="header" class="title">` bude **zelený**, protože ID má nejvyšší specificitu (`0,1,0,0` přebíjí ostatní).

#### Specificita se nepřevádí jako desítkové číslo

Zápis "100 + 10 + 1 = 111" funguje v praxi, ale **není to přesné**, protože v každé pozici může být víc než 10 hodnot. Lepší je číst to jako tuple (n-tice) `(a, b, c, d)` a porovnávat zleva doprava.

```css
/* 11 tříd vs 1 ID: kdo vyhraje? */
.a.a.a.a.a.a.a.a.a.a.a  /* 0,0,11,0 */
#id                      /* 0,1,0,0  ← vyhrává */
```

ID stále vyhrává, protože `0,1` je víc než `0,0` (porovnává se zleva).

## Dědičnost (inheritance)

**Dědičnost** znamená, že některé CSS vlastnosti se automaticky přenášejí z rodičovského elementu na potomky. Není to vítězství selektoru, je to úplně jiný mechanismus.

### Co dědí, co nedědí

| Dědí se *(typicky textové vlastnosti)* | Nedědí se *(typicky vizuální/layoutové)* |
| --- | --- |
| `color` | `margin`, `padding`, `border` |
| `font-family`, `font-size`, `font-weight` | `width`, `height` |
| `line-height` | `background` |
| `text-align`, `text-indent` | `display`, `position` |
| `visibility` | `box-shadow` |
| `cursor` | `overflow` |

```css
body {
	color: navy;     /* všechny vnořené elementy budou navy **/
	border: 1px;     /** tohle se nezdědí, je to jen na body */
}
```

### Vynucení dědičnosti

Vlastnosti, které normálně nedědí, můžeš přinutit přes klíčové slovo `inherit`:

```css
.child {
  border: inherit;   /* "vezmi border z rodiče" */
}
```

### Speciální klíčová slova

| Klíčové slovo | Význam |
| --- | --- |
| `inherit` | Použij hodnotu rodiče |
| `initial` | Použij výchozí hodnotu vlastnosti dle specifikace |
| `unset` | `inherit` u dědičných, `initial` u nedědičných |
| `revert` | Vrať se k hodnotě z předchozího origin (typicky UA styl) |
| `revert-layer` | Vrať se k hodnotě z předchozí kaskádové vrstvy |

### Vztah k specificitě

Důležitý chyták: **přímo nastavená vlastnost přebíjí zděděnou, i když má nižší specificitu**. Dědičnost se aplikuje až tehdy, když pro daný element není žádné pravidlo pro danou vlastnost.

# Pořadí pravidel

Pokud mají dvě pravidla **stejnou specificitu i původ**, vyhraje to, které je v kódu **později**.

```css
p { color: blue; }
p { color: red; }   /* vyhrává: je níž */
```

Stejně tak importované CSS soubory: pozdější `@import` přebíjí dřívější.

```css
@import "base.css";
@import "overrides.css";   /* tyhle styly vyhrávají při shodné specificitě */
```

# Selektory

### Základní selektory

| Selektor | Znak | Příklad | Co dělá |
| --- | --- | --- | --- |
| Typový (element) | název | `p { }` | Vybere všechny `<p>` |
| Třída | `.` | `.v2 { }` | Elementy s danou třídou |
| ID | `#` | `#menu { }` | Element s daným ID (mělo by být unikátní) |
| Univerzální | `*` | `* { }` | Všechny elementy |
| Atributový | `[]` | `[type="text"] { }` | Elementy s daným atributem |

### Atributové selektory podrobně

```css
[href]              /* má atribut href (jakákoli hodnota) */
[type="email"]      /* přesná shoda hodnoty */
[href^="https"]     /* začíná na "https" (^ jako v regexu) */
[href$=".pdf"]      /* končí na ".pdf" */
[class*="icon"]     /* obsahuje "icon" kdekoli */
[lang|="en"]        /* "en" nebo "en-cokoli" (pro jazykové kódy) */
[class~="primary"]  /* obsahuje "primary" jako celé slovo */
```

### Kombinátory

| Kombinátor | Znak | Příklad | Co dělá |
| --- | --- | --- | --- |
| Potomka | mezera | `div p` | Všechny `<p>` uvnitř `<div>` (libovolná hloubka) |
| Přímého dítěte | `>` | `ul > li` | Jen přímé děti |
| Přímého sourozence | `+` | `h1 + p` | `<p>` ihned po `<h1>` |
| Obecného sourozence | `~` | `h1 ~ p` | Všechny `<p>` po `<h1>` na stejné úrovni |

### Pseudo-třídy (`:`)

Vybírají elementy podle **stavu nebo pozice**. Specificita jako třída: `0,0,1,0`.

```css
/* Stavové */
a:hover               /* kurzor myši nad elementem */
input:focus           /* aktivní pole */
input:focus-visible   /* focus jen při klávesnicové navigaci */
button:active         /* v momentě kliknutí */
input:disabled        /* zakázané */
input:checked         /* zaškrtnuté */
input:required        /* povinné */
input:valid           /* validní hodnota */
input:invalid         /* nevalidní hodnota */

/* Strukturální */
li:first-child        /* první mezi sourozenci */
li:last-child         /* poslední */
li:only-child         /* jediné dítě */
li:nth-child(2n)      /* každý sudý */
li:nth-child(odd)     /* každý lichý */
li:nth-child(3n+1)    /* každý třetí počínaje prvním */

/* Logické */
div:empty             /* bez obsahu */
p:not(.special)       /* všechny <p> kromě .special */

/* Speciální */
:root                 /* kořen dokumentu (<html>) */
:target               /* element, na který odkazuje URL fragment (#kotva) */
:lang(cs)             /* podle atributu lang */
```

### Moderní selektory: `:is()`, `:where()`, `:has()`

### `:is()` (Level 4, široká podpora od 2021)

Zkrácení skupin selektorů. Specificita se bere podle **nejvyššího** argumentu.

```css
/* Místo: */
header h1, header h2, header h3 { color: red; }

/* Můžeš napsat: */
header :is(h1, h2, h3) { color: red; }
```

### `:where()` (Level 4)

Stejné jako `:is()`, ale se **specificitou 0**. Skvělé pro CSS resety a knihovny, které nechceš, aby přebíjely uživatelský kód.

```css
/* Specificita: 0,0,0,0 (úplně nula) */
:where(button, .btn, [type="button"]) {
cursor: pointer;
}

/* Tvoje vlastní pravidlo to lehce přebije **/
button { cursor: default; }  /** vyhraje */
```

### `:has()` (Level 4, široká podpora od 2023)

**"Rodičovský selektor"**, na který CSS čekalo 20+ let.
Vybírá element, který **obsahuje** určitého potomka.

```css
/* Article, který obsahuje obrázek */
article:has(img) { padding: 2rem; }

/* Form group, který obsahuje invalid input */
.form-group:has(input:invalid) { border-color: red; }

/* Body bez navigace (negace) */
body:not(:has(nav)) { padding-top: 0; }
```

Tohle bylo dlouho nemožné bez JavaScriptu. Dnes nativní CSS.

### Pseudo-elementy (`::`)

Stylují **části elementu** nebo generovaný obsah. Specificita jako element: `0,0,0,1`.

```css
p::before        { content: "→ "; }      /* vloží obsah před element */
p::after         { content: " ←"; }      /* vloží obsah za element */
p::first-letter  { font-size: 2em; }     /* první písmeno */
p::first-line    { font-weight: bold; }  /* první řádek */
::selection      { background: yellow; } /* označený text */
::placeholder    { color: gray; }        /* placeholder v inputu */
::marker         { color: red; }         /* odrážka u li */
```

> jedna dvojtečka `:` označuje pseudo-**třídu** (stav), dvě dvojtečky `::` pseudo-**element** (část)
> 

# **Media queries a container queries**

### `@media`

Aplikují CSS podmíněně podle vlastností zařízení nebo preferencí uživatele:

```css
@media (max-width: 768px) {
  .menu { display: none; }
}

/* Kombinování */
@media (min-width: 768px) and (orientation: landscape) {
  .sidebar { width: 300px; }
}

/* Tmavý režim */
@media (prefers-color-scheme: dark) {
  body { background: #1a1a1a; color: white; }
}

/* Méně animací (pro citlivé uživatele) */
@media (prefers-reduced-motion: reduce) {
  * { animation-duration: 0.01ms !important; }
}

/* Tisk */
@media print {
  nav, footer { display: none; }
}
```

### `@container` (Container queries)

Místo dotazování na šířku obrazovky se ptáš na šířku **kontejneru komponenty**. Široká podpora od 2023.

```css
.card-container {
  container-type: inline-size;
  container-name: card;
}

@container card (min-width: 400px) {
  .card {
    display: grid;
    grid-template-columns: 1fr 2fr;
  }
}
```

Komponenta se přizpůsobí kontextu, do kterého ji vložíš. Skvělé pro design systémy.

# CSS proměnné (Custom Properties)

CSS proměnné jsou plnohodnotnou součástí kaskády: dědí se, lze je přepisovat v různých scopech, fungují s media queries.

```css
:root {
  --primary-color: #3b82f6;
  --font-size-base: 16px;
  --spacing-md: 1rem;
}

.button {
  background-color: var(--primary-color);
  font-size: var(--font-size-base);
  padding: var(--spacing-md);
}

/* Override v menším scopu */
.dark-section {
  --primary-color: #93c5fd;  /* uvnitř téhle sekce všechny .button budou světle modré */
}
```

### Dark mode přes proměnné

```css
:root {
  --bg: white;
  --text: black;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #1a1a1a;
    --text: white;
  }
}

body { background: var(--bg); color: var(--text); }
```

### `var()` s fallbackem

```css
.button {
  color: var(--btn-color, blue);  /* pokud --btn-color není definované, použij blue */
}
```

# CSS izolace

Izolace zajišťuje, aby styly jedné části aplikace neovlivnily jinou.

| Technika | Jak funguje |
| --- | --- |
| **Konvence pojmenování** (BEM) | Prefixy a jasné názvy, zabraňují kolizím |
| **CSS Modules** | Bundler vygeneruje unikátní názvy tříd (`.button_a1b2c3`) |
| **CSS-in-JS** (styled-components) | Styly v JS kódu, automaticky scoped |
| **Shadow DOM** | Kompletní izolace v Web Components, žádné styly z venku se nedostanou dovnitř |
| **`@scope`** | Nativní CSS pravidlo pro omezení působnosti (novější) |

### `@scope` (moderní)

```css
@scope (.card) to (.card-content) {
  /* styly platí jen v .card, ale ne hlouběji než .card-content */
  h2 { color: blue; }
}
```

# BEM (Block Element Modifier)

**BEM** je konvence pro pojmenovávání CSS tříd. Hlavní cíl: **nulová závislost na hierarchii HTML** a žádné konflikty.

```css
.block                              /* samostatná komponenta */
.block__element                     /* část bloku (dvě podtržítka) */
.block--modifier                    /* varianta bloku (dvě pomlčky) */
.block__element--modifier           /* varianta části */
```

### Příklad: tlačítko

```css
<button class="button button--primary">
  <span class="button__icon">→</span>
  <span class="button__label">Odeslat</span>
</button>
```

```css
.button                    { padding: 8px 16px; }   /* blok **/
.button--primary           { background: blue; }    /** modifier **/
.button--disabled          { opacity: 0.5; }        /** modifier **/
.button__icon              { margin-right: 4px; }   /** element **/
.button__label             { font-weight: 600; }    /** element **/
.button__icon--small       { font-size: 12px; }     /** element + modifier */
```

### Proč BEM funguje

1. **Selektory mají vždy specificitu `0,0,1,0`**, nikdy nemusíš počítat kaskádu.
2. **Žádné kombinátory** (`.card .title`), takže můžeš HTML kdykoli změnit.
3. **Žádné konflikty mezi komponentami**, protože každá komponenta má vlastní prefix.
4. **Kód je samodokumentující**: z `.modal__close-button--danger` poznáš kontext i variantu.

### Co BEM **není**

BEM **není** o tom mít všude třídy. Někdy je v pořádku stylovat `<h2>` uvnitř `.card`. BEM se hodí pro **znovupoužitelné komponenty**, ne pro jednorázový obsah.

# Kaskádové vrstvy (`@layer`)

Dimenze kaskády **nad specificitou**. Vrstvám přiřadíš pořadí, a pravidla v dřívější vrstvě **prohrávají**, i kdyby měla vyšší specificitu.

```css
/* Definice pořadí vrstev (zleva nejnižší, zprava nejvyšší) */
@layer reset, base, components, utilities;

@layer reset {
  * { margin: 0; padding: 0; }
}

@layer components {
  .card { padding: 1rem; border: 1px solid; }
}

@layer utilities {
  .p-0 { padding: 0; }   /* přebije .card, i když má nižší specificitu */
}

/* Styly mimo vrstvy přebíjejí všechny styly ve vrstvách */
.special { color: red; }
```

### Proč to existuje

Před `@layer` bylo nutné používat `!important` nebo specificity hacky pro zajištění, že utility classes přebijí komponenty. Teď je to čistě deklarativní.

Pořadí v kaskádě po `@layer`:

1. Pravidla v dřívějších vrstvách
2. Pravidla v pozdějších vrstvách
3. Pravidla mimo všechny vrstvy (vždy nejvyšší ze "normálních")
4. `!important` v opačném pořadí (mimo vrstvy nejnižší, v dřívějších vrstvách nejvyšší)

# CSS nesting (vnořování)

Nativní vnořování CSS, široká podpora od 2023. Dřív jen v preprocesorech (Sass, Less).

```css
.card {
  padding: 1rem;
  border: 1px solid;

  & .title {
    font-size: 1.5rem;
  }

  &:hover {
    background: #f5f5f5;
  }

  @media (min-width: 768px) {
    padding: 2rem;
  }
}
```

# Konvence a best practices

### Metodologie pojmenování

| Metodika | Hlavní myšlenka |
| --- | --- |
| **BEM** | `Block__Element--Modifier`, nejrozšířenější |
| **OOCSS** | Odděluje strukturu od vizuálu (`.media` + `.media--inverted`) |
| **SMACSS** | Kategorie: base, layout, module, state, theme |
| **Utility-first** | Malé jednoúčelové třídy, kombinace v HTML (Tailwind CSS) |
| **CSS Modules** | Lokálně scoped třídy generované buildem (React, Vue) |

### DRY princip

```css
/* ✗ Opakování */
.btn-blue  { padding: 8px 16px; border-radius: 4px; background: blue; }
.btn-red   { padding: 8px 16px; border-radius: 4px; background: red; }

/* ✓ DRY: sdílený základ */
.btn        { padding: 8px 16px; border-radius: 4px; }
.btn--blue  { background: blue; }
.btn--red   { background: red; }
```

### Mobile-first

Začni stylovat pro **nejmenší obrazovku**, větší řeš přes `min-width` media queries. Mobilní verze je default, ne výjimka.

### Co dělat a co nedělat

- ✓ Nízká specificita, ploché selektory (jen třídy).
- ✓ Komponentový přístup: jedna komponenta = jeden soubor.
- ✓ CSS proměnné pro design tokens (barvy, mezery, fonty).
- ✗ `!important` jako řešení problému se specificitou.
- ✗ ID selektory v CSS (`#header { }`), specificita je moc vysoká.
- ✗ Hluboce vnořené selektory (`.nav ul li a span { }`).
- ✗ Inline styly v HTML, pokud to není dynamicky generovaná hodnota.

## Tipy pro ústní zkoušku

### Jak začít

> *"Kaskáda v CSS je mechanismus, kterým prohlížeč rozhodne, které pravidlo aplikovat, když na jeden element cílí víc pravidel. Vyhodnocují se tři věci v pořadí: původ a důležitost, specificita, a pořadí v kódu. K tomu existuje dědičnost, což je samostatný mechanismus přenosu hodnot z rodiče na potomka."*
> 

### Co komise typicky chce slyšet

- Tři faktory kaskády (origin, specificita, pořadí) v tomto pořadí.
- Specificita jako čtyřmístné číslo, příklad výpočtu.
- Rozdíl pseudo-třída `:` vs pseudo-element `::`.
- Že `!important` je krajní řešení.
- BEM s konkrétním příkladem na tabuli.
- Media queries s `prefers-color-scheme` jako moderní bonus.

### Doplňky, které komisi potěší

- **Dědičnost** jako součást kaskády a klíčová slova `inherit`/`initial`/`unset`.
- **Moderní selektory** `:is()`, `:where()`, `:has()`, hlavně `:has()` jako dlouho očekávaný "rodičovský selektor".
- **`@layer`** a **`@scope`** jako moderní řešení izolace.
- **CSS nesting** nativní od 2023.
- **Container queries** jako alternativa k media queries.
- **CSS jako modulární standard**, ne "CSS3".