Skip to content

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
1Normální styly prohlížeče (user-agent)
2Normální styly uživatele (extensions, accessibility)
3Normální styly autora (tvoje CSS)
4CSS animace (@keyframes během animace)
5!important styly autora
6!important styly uživatele
7!important styly prohlížeče
8CSS 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).

SelektorSpecificitaPříklad
Inline styl1,0,0,0style="color: red"
ID selektor0,1,0,0#header { }
Třída, pseudo-třída, atribut0,0,1,0.button, :hover, [type]
Element, pseudo-element0,0,0,1p { }, ::before
Univerzální *0,0,0,0* { }
:not(), :is(), :has()dle nejvyšší specificity argumentuviz 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é)
colormargin, padding, border
font-family, font-size, font-weightwidth, height
line-heightbackground
text-align, text-indentdisplay, position
visibilitybox-shadow
cursoroverflow
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é slovoVýznam
inheritPoužij hodnotu rodiče
initialPoužij výchozí hodnotu vlastnosti dle specifikace
unsetinherit u dědičných, initial u nedědičných
revertVrať se k hodnotě z předchozího origin (typicky UA styl)
revert-layerVrať 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

SelektorZnakPříkladCo dělá
Typový (element)názevp { }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átorZnakPříkladCo dělá
Potomkamezeradiv pVšechny <p> uvnitř <div> (libovolná hloubka)
Přímého dítěte>ul > liJen přímé děti
Přímého sourozence+h1 + p<p> ihned po <h1>
Obecného sourozence~h1 ~ pVš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.

TechnikaJak funguje
Konvence pojmenování (BEM)Prefixy a jasné názvy, zabraňují kolizím
CSS ModulesBundler vygeneruje unikátní názvy tříd (.button_a1b2c3)
CSS-in-JS (styled-components)Styly v JS kódu, automaticky scoped
Shadow DOMKompletní izolace v Web Components, žádné styly z venku se nedostanou dovnitř
@scopeNativní 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í

MetodikaHlavní myšlenka
BEMBlock__Element--Modifier, nejrozšířenější
OOCSSOdděluje strukturu od vizuálu (.media + .media--inverted)
SMACSSKategorie: base, layout, module, state, theme
Utility-firstMalé jednoúčelové třídy, kombinace v HTML (Tailwind CSS)
CSS ModulesLoká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".