Vzhled
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 interaktivitaCo 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í:
- Původ a důležitost (odkud styl pochází a jestli má
!important) - Specificita (jak přesný je selektor)
- 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
- Selektory mají vždy specificitu
0,0,1,0, nikdy nemusíš počítat kaskádu. - Žádné kombinátory (
.card .title), takže můžeš HTML kdykoli změnit. - Žádné konflikty mezi komponentami, protože každá komponenta má vlastní prefix.
- Kód je samodokumentující: z
.modal__close-button--dangerpoznáš 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:
- Pravidla v dřívějších vrstvách
- Pravidla v pozdějších vrstvách
- Pravidla mimo všechny vrstvy (vždy nejvyšší ze "normálních")
!importantv 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).
- ✗
!importantjako ř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
!importantje krajní řešení. - BEM s konkrétním příkladem na tabuli.
- Media queries s
prefers-color-schemejako 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". @layera@scopejako moderní řešení izolace.- CSS nesting nativní od 2023.
- Container queries jako alternativa k media queries.
- CSS jako modulární standard, ne "CSS3".