Съдържание на курса
Урок 3 — CSS: каскада, специфичност и кутия
От Dorian Chávez · основател на Hábil и архитект на интеграции ·
Време: две сесии от около 90 минути. Разпределение, което работи: в първата — „Защо, преди как“ и каскадата (3.1), с нейните фигури за предвиждане кое правило печели; във втората — кутията (3.2), променливите (3.3), четливото табло, „Грешката, която ще видиш“ и упражненията. Всяка сесия завършва със страница, която можеш да отвориш и да измериш.
Какво изграждаш: таблото revisor, вече четливо: типография, цветове, подредена таблица и значките за състояние
Какво научаваш: откъде идва всеки стил и кой печели; моделът на кутията и box-sizing; променливи за цвят и типография
Страниците на този урок. Всички фигури са в programas/03-css-cascada-caja/ на хранилището на курса, всяка с очаквания си изход до нея; стиловият лист на четливото табло е в fig03_08/styles.css. Отваряй ги с локалния си сървър, както в Урок 1.
След урока ще можеш да
- Свързваш стилов лист към страница и пишеш правила със селектори за елемент, за клас, за идентификатор и за състояние.
- Предвиждаш кое правило печели, когато две се сблъскат, като изчисляваш специфичността им и прилагаш реда на каскадата, без да прибягваш до
!important. - Изчисляваш на ръка реалната ширина на една кутия с
content-boxи сborder-boxи я проверяваш в инструментите на браузъра. - Декларираш променливи за цвят, пространство и типография и ги ползваш, така че промяната на едно решение да се прави на едно място.
- Проверяваш с числа, че цветът на текста отговаря на минималния контраст и че фокусът на клавиатурата се вижда.
- Четеш съобщенията на CSS валидатора и разпознаваш двете грешки, които не произвеждат никакво съобщение.
Защо, преди как
Когато завърши Урок 2, таблото работеше и изглеждаше като страница от 1995 г.: шрифт със засечки, сив системен бутон, таблица без линии, в която колоните се лепят една за друга. Това не е недостатък на HTML; това е, което браузърът прави, когато никой не му казва как да чертае. Всеки браузър носи свой собствен стилов лист и това е онзи, който си гледал.
Днес му казваме как искаме да изглежда. Таблото трябва да може да се чете: четлив шрифт, размери, които създават йерархия, таблица с разделени редове и подравнени числа и цветна значка до състоянието на всяка услуга, така че с един поглед да се различава какво е наред от какво е паднало. Това е видимият резултат.
Онова, което наистина научаваш днес, е друго и то е, което отличава този, който пише CSS, от този, който го изстрадва. Почти всички проблеми с CSS са от един от два вида: „моето правило не се прилага“ и „моята кутия не е с размера, който написах“. Първият се нарича каскада, а вторият — модел на кутията. Който не разбира каскадата, решава всеки конфликт, като вдига силата: повече селектори, идентификатор, !important, а после още един !important, за да победи първия. Който не разбира кутията, в крайна сметка маха пиксели на око, докато нещо се побере. И двамата завършват със стилов лист, в който никой не смее да пипне нищо.
Затова урокът върви в този ред. Първо каскадата (раздел 3.1): как браузърът решава, между няколко правила, които се бият за един и същ елемент, кое остава. После кутията (3.2): колко наистина е голям всеки елемент. И накрая променливите (3.3), които ти позволяват да запишеш всяко решение само веднъж: цвета на текста, разстоянието, шрифта. Във всеки раздел има страница, която можеш да отвориш, промениш и счупиш. Тази в края е цялото табло.
Един критерий минава през всичко: CSS от този урок не скрива нищо от наученото в предишния. Фокусът на клавиатурата се вижда, състоянието не зависи само от цвета, текстовете имат достатъчен контраст. Проверява се с числа, не на око.
Понятията
3.1 Откъде идва всеки стил и кой печели
3.1.1 Едно правило, от край до край
Един стилов лист е списък от правила. Всяко правило има две половини: селектор, който казва към кои елементи се прилага, и блок с декларации между фигурни скоби, който казва какво се променя в тях. Всяка декларация е двойка свойство: стойност; и се затваря с точка и запетая.
<!-- fig03_01.html -->
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Mi primer estilo</title>
<style>
h1 {
color: #0b5cad;
}
p {
max-width: 40rem;
line-height: 1.6;
}
</style>
</head>
<body>
<main>
<h1>Revisor de servicios</h1>
<p>Cada regla tiene un selector, que dice a qué elementos se aplica, y declaraciones entre llaves, que dicen qué cambia en ellos.</p>
</main>
</body>
</html>
Revisor de servicios
Cada regla tiene un selector, que dice a qué elementos se aplica, y declaraciones entre llaves, que dicen qué cambia en ellos.
В тази страница h1 е селектор, който значи „всички заглавия от първо ниво“; декларацията color: #0b5cad; променя цвета на текста на син, записан в шестнайсетичен вид (две цифри за червеното, две за зеленото и две за синьото). Второто правило ограничава ширината на абзаците до 40rem (rem е единица, която е равна на размера на буквите на страницата, 16 пиксела по подразбиране, така че са 640 пиксела; ще я разгледаш в дълбочина в 3.2.3) и разделя редовете с височина 1.6 пъти размера на буквите. Изходният текст се вижда същият като без стилове и това е причината в този курс, освен текста, да се проверяват и изчислените стойности: цветът на този <h1>, измерен в Chrome 154, е rgb(11, 92, 173), което е същият син в друга нотация.
Има три начина да свържеш CSS към страница и е добре да знаеш и трите, за да знаеш защо се предпочита един:
- Външен стилов лист, с
<link rel="stylesheet" href="styles.css">вътре в<head>. Това е онзи, който ще ползваш в таблото. Браузърът го пази в паметта и го използва повторно във всички страници на сайта, а същият файл може да се отваря и променя, без да се пипа HTML. - Блок
<style>вътре в<head>, както по-горе. Удобен е за експеримент с една страница и затова фигурите на този урок го ползват. В истински сайт е добре да се избягва, по една причина за сигурност, която ще видиш в Урок 11. - Атрибутът
style, поставен върху елемент (<p style="color: red">). Има особена сила в каскадата, която ще видиш веднага, и точно затова почти никога не е препоръчителен: смесва външния вид със съдържанието и е много труден за побеждаване без трикове.
3.1.2 Селекторите, които ти трябват днес
Селекторът е въпрос, който браузърът задава на всеки елемент: „ти ли си?“. Ето тези, които ще ползваш в таблото, от най-общия до най-точния:
- По тип (по име на елемент):
p,table,h2. Прилага се към всички от този тип. - По клас:
.status. Точката показва, че търси елементи, чийто атрибутclassсъдържа това име. Един елемент може да има няколко класа, разделени с интервали (class="status status-down"), а един клас може да е в много елементи. Това е основният инструмент на CSS и е причината в Урок 2 да сложишclassна значките. - По идентификатор:
#summary. Знакът диез (#) търси единствения елемент с тозиid. Единidне може да се повтаря в страница. - По атрибут:
input[type="search"]. Квадратни скоби: елементи, които имат този атрибут с тази стойност. - По псевдоклас:
a:hover,:focus-visible,td:last-child. Двете точки сочат състояние или позиция на елемента: когато мишката е отгоре, когато клавиатурата го държи на фокус, когато е последното дете на своя родител. - По наследник:
dl div(с интервал). Елементиdiv, които са вътре вdl, на каквато и да е дълбочина. - Списък:
th, td(със запетая). Съкращение за две правила с едни и същи декларации.
Селекторът се чете отдясно наляво: в dl div:last-child целта е div, което е последно дете, и се брои само ако освен това е вътре в dl. Селекторите могат да се комбинират без интервал: p.alert е <p>, който освен това има класа alert.
3.1.3 Когато две правила се сблъскат: каскадата
Тук е централното понятие на урока. Почти никога няма само едно правило върху един елемент. Има стиловия лист на браузъра, твоя стилов лист, още едно правило по-надолу в него, а понякога и атрибут style. Когато няколко правила дават различни стойности на едно и също свойство на един и същ елемент, браузърът трябва да избере едно. Думата каскада от „каскадни стилови листове“ (CSS, по английската абревиатура) е името на алгоритъма, с който избира. Не е магия, нито случайност: това са стъпки, в строго определен ред, който е записан в спецификацията, и първата, която реши равенството, решава всичко.
Ето списъка, обобщен и в реда, в който се прилага. За всяко свойство на всеки елемент браузърът:
- Отхвърля правилата, които не се прилагат: тези, които не избират този елемент или носят невалидна стойност.
- Сравнява произхода и важността. Има три произхода: стиловият лист на браузъра, този на потребителя (предпочитанията на читателя, които почти никой не ползва) и твоят, този на автора. Между нормални декларации печели авторската. С
!importantредът се обръща, за да може читател със специални нужди да победи автора. - Сравнява стиловете на атрибута
style. Декларация, поставена в атрибутаstyle, печели пред тези от стиловите листове, независимо от специфичността им. - Сравнява слоевете (
@layer), механизъм, който ще видиш в 3.1.5. Между нормални декларации тези в по-късно декларирания слой печелят пред тези в по-ранния слой; а тези, които не са в никакъв слой, печелят пред всички, които са. С!importantтози ред се обръща, както при произходите. - Сравнява специфичността на селектора, което е темата на следващата точка.
- Ако всичко предишно е равно, печели последното. Правилото, което се появява най-долу в стиловия лист.
Две неща имат значение в този списък. Първо: редът на правилата е последният критерий, не първият. Много хора вярват, че „печели последното правило“ и се объркват, когато не става така. Печели последното само когато предишните критерии са били равни. Второ: всяка стъпка решава изцяло. Ако една стъпка даде победител, следващите дори не се поглеждат; правило с десет идентификатора в селектора си не може да победи декларация с !important.
3.1.4 Специфичност: броене с три цифри
Специфичността е петата стъпка, тази, която почти винаги решава. Тя е мярка за колко точен е един селектор и се изчислява с три цифри, които се записват (A, B, C):
- A брои идентификаторите (
#summary). - B брои класовете, атрибутите и псевдокласовете (
.status,[type="search"],:hover,:last-child). - C брои типовете елементи (
p,table) и псевдоелементите (::before).
Универсалният селектор * не добавя нищо. И цифрите се сравняват отляво надясно, една по една, не като трицифрено число: първо A; ако са равни, B; ако са равни, C. Един-единствен идентификатор (1,0,0) побеждава всякакъв брой класове, дори хиляда, защото A се сравнява първо. Затова браузърът вижда #summary като много по-силен от .status.
Няколко примера, с тяхната сметка:
| Селектор | Сметка (A, B, C) | Защо |
|---|---|---|
p |
(0, 0, 1) | един тип |
.alert |
(0, 1, 0) | един клас |
p.alert |
(0, 1, 1) | един клас и един тип |
a:hover |
(0, 1, 1) | един псевдоклас и един тип |
input[type="search"] |
(0, 1, 1) | един атрибут и един тип |
dl div:last-child |
(0, 1, 2) | един псевдоклас и два типа |
#summary dd |
(1, 0, 1) | един идентификатор и един тип |
:where(.card) p |
(0, 0, 1) | :where() винаги струва нула |
Последният ред заслужава обяснение. :where() и :is() са псевдокласове, които приемат списък от селектори. Разликата между тях е точно специфичността им: :where() винаги струва нула, а :is() взима специфичността на най-специфичния от аргументите си. С :where() можеш да напишеш базов стил, който всеки може да презапише без усилие. Ще я ползваш за това по-нататък и я виждаш в употреба в първото упражнение.
Да видим всичко заедно. Следващата страница има четири абзаца и пет правила за цвят, които се оспорват:
<!-- fig03_02.html -->
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>¿Quién gana?</title>
<style>
p { color: dimgray; }
.alert { color: saddlebrown; }
p.alert { color: crimson; }
#three, #four { color: royalblue; }
p { color: black; }
</style>
</head>
<body>
<main>
<h1>¿Quién gana?</h1>
<p>Uno: solo el nombre del elemento.</p>
<p class="alert">Dos: con una clase.</p>
<p class="alert" id="three">Tres: con clase e identificador.</p>
<p class="alert" id="four" style="color: darkgreen">Cuatro: con el atributo style.</p>
</main>
</body>
</html>
¿Quién gana?
Uno: solo el nombre del elemento.
Dos: con una clase.
Tres: con clase e identificador.
Cuatro: con el atributo style.
Преди да прочетеш отговора, направи собствен залог с двата списъка по-горе. Измерих изчислените цветове в Chrome 154 и ето какво излезе:
- Едно е черно (
rgb(0, 0, 0)). Прилагат му се самоp { dimgray }иp { black }. И двете имат една и съща специфичност, (0,0,1), така че решава редът и печели последното: черно. Това е случаят, в който „последното печели“ е вярно. - Две е аленочервено (
rgb(220, 20, 60)). Прилагат му се три правила:p(0,0,1),.alert(0,1,0) иp.alert(0,1,1). Печели това с по-голяма специфичност,p.alert, макар да е предиp { black }. Редът не се поглежда, защото предишната стъпка вече е решила. - Три е кралско синьо (
rgb(65, 105, 225)). Идентификаторът му дава (1,0,0) и побеждава всички предишни правила. - Четири е тъмнозелено (
rgb(0, 100, 0)). Има същия идентификатор като Три, но атрибутътstyleсе сравнява преди специфичността и печели. Това е случаят, който раздел 3.1.1 обяви: затоваstyle, поставен в HTML, е толкова труден за побеждаване от стиловия лист.
Има една сметка, която трябва да направиш наум, като видиш този резултат: елемент Три има пет правила за цвят, които го избират (двете на p, .alert, p.alert и това на идентификатора), и онова, което е спечелило, е най-малко приличащото на „последното, което написах“. Ако не разбираш реда на списъка, единственият инструмент, който ти остава, е да вдигаш силата, а силата може да се вдига само донякъде.
3.1.5 Слоеве: чистият изход
Слоевете (@layer) съществуват за случаите, когато критериите за специфичност стават пречка. Слоят е група правила с име; декларираш реда на слоевете веднъж и между слоевете командва този ред, без да се гледа специфичността.
<!-- fig03_03.html -->
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Capas</title>
<style>
@layer base, components, overrides;
@layer base {
a { color: gray; }
}
@layer components {
.link { color: green; }
}
@layer overrides {
a { color: blue; }
}
.plain { color: purple; }
</style>
</head>
<body>
<main>
<h1>Capas</h1>
<p><a href="#uno" class="link">Un enlace con clase, en la capa components</a></p>
<p><a href="#dos">Un enlace sin clase</a></p>
<p><a href="#tres" class="plain">Un enlace con una regla sin capa</a></p>
</main>
</body>
</html>
Capas
Un enlace con clase, en la capa components
Un enlace sin clase
Un enlace con una regla sin capa
Първият ред на блока <style> декларира реда: base, после components, после overrides. Последният слой печели. Виж първата връзка: селекторът .link има специфичност (0,1,0), а селекторът a от слоя overrides — само (0,0,1); в стилов лист без слоеве щеше да спечели .link. Тук печели a и връзката е синя (rgb(0, 0, 255)), защото слоят ѝ е след това. Втората връзка също е синя. Третата е лилава: .plain не е в никакъв слой, а нормалните декларации без слой печелят пред всички, които имат слой.
Това е, което прави слоевете полезни. Можеш да подредиш стиловия си лист в реда, в който искаш нещата да печелят (нулиране в началото, после стиловете на тялото, после компонентите), а специфичността се брои само вътре във всеки слой. Никой не се нуждае от !important, за да победи правило, което е в по-ранен слой. А правилото без слой е аварийната врата: ако някой напише стилов лист без слоеве, той печели пред всичко. Всичко това важи за нормалните декларации; спецификацията обръща реда за тези с !important: между тях печели първият слой, а декларация с !important вътре в слой побеждава такава с !important без слой. Още една причина да не го ползваш: чупи интуицията, че последното командва.
Слоевете са широко достъпни в браузърите от март 2022 г.; референтният сайт на Mozilla ги отбелязва като „Widely available“. Таблото от днес ги ползва.
3.1.6 Наследяването: онова, което не иска правило
Липсва една част и тя е тази, която обяснява защо понякога един елемент има стил, без никое правило да му говори. Някои свойства се наследяват: ако един елемент няма собствена стойност, взима тази на родителя си. Цветът на текста, шрифтът, размерът и височината на реда се наследяват. Други не: външният отстъп, вътрешният отстъп, рамката и фонът са на всеки елемент и не преминават към децата му (ако рамката се наследяваше, всеки абзац вътре в една карта щеше да има собствена рамка).
<!-- fig03_04.html -->
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Herencia</title>
<style>
article {
color: darkgreen;
font-style: italic;
border: 2px solid black;
padding: 1rem;
}
</style>
</head>
<body>
<main>
<h1>Herencia</h1>
<article>
<p>Este párrafo hereda el color y la cursiva, pero no el borde.</p>
<p><label>Un campo: <input type="text" value="no hereda la fuente"></label></p>
</article>
</main>
</body>
</html>
Herencia
Este párrafo hereda el color y la cursiva, pero no el borde.
Un campo:
Измерих го в Chrome 154. Абзацът има тъмнозеления цвят и курсива на <article> (наследява ги), а рамката му е 0px: не я е наследил. Етикетът, който е текстов елемент, също наследява зеленото. А текстовото поле — не: изчисленият му цвят е черен, а курсивът му е normal. Едно поле на форма не наследява от родителя си нито шрифта, нито цвета, защото стиловият лист на браузъра им дава собствени стойности. На моя компютър полето ползваше Arial на 13.33 px, докато абзацът ползваше 16 px; точните стойности зависят от операционната система, но разликата съществува във всички. Това е причината таблото да носи правилото font: inherit за своите полета и бутони: без него полето за търсене изглежда като от друга страница.
Има четири думи, които позволяват да командваш на ръка над наследяването: inherit (взима стойността на родителя, дори свойството нормално да не се наследява), initial (началната стойност от спецификацията), unset (наследява, ако свойството е наследяемо, а ако не, ползва началната) и revert (отменя стиловете на автора и се връща към стойността, която би поставил предишният произход: стиловият лист на потребителя, ако съществува, а ако не, този на браузъра). От тях inherit е тази, която ще ползваш.
И едно предупреждение за реда, в който става всичко. Наследяването е най-слабото, което съществува: наследена стойност губи пред всяко правило, което сочи елемента, колкото и слабо да е. Затова * { color: black }, със специфичност нула, чупи цвета, наследен от целия документ: „говори“ на всеки елемент директно.
3.1.7 !important и как се стига до нужда от него
Декларацията !important стои в края на една стойност (color: red !important;) и променя стъпката ѝ в каскадата: преминава в група, която побеждава всички нормални декларации, с какъвто и да е селектор. Това е авариен инструмент с един легитимен случай на употреба, а именно стиловият лист на потребител, който има нужда от едър шрифт. За този, който пише авторски стилов лист, това почти винаги е знак, че има неразбран конфликт.
Пътят към войната на !important е винаги един и същ. Едно правило не печели; вдига му се специфичността; вдига се тази на съседно правило; някой добавя !important; правилото, което трябваше да го победи, се нуждае от още един !important; и оттам нататък всяка промяна изисква още един. Изходът не е да вдигаш силата, а да я сваляш: да пишеш селектори с най-ниската специфичност, която работи, и да оставиш реда, слоевете и :where() да свършат работата. В четвъртото упражнение имаш стилов лист с този проблем, за да го развържеш.
Един инструмент ти го улеснява много: инспекторът на браузъра. Щракни с десния бутон върху какъвто и да е елемент и избери „Инспектиране“. В панела със стилове ще видиш всички правила, които му се прилагат, в реда, в който ги оценява каскадата, а тези, които са загубили, се появяват зачеркнати. В панела с изчислените стойности (Computed) ще видиш крайната стойност на всяко свойство и, като го разгънеш, кое правило я е поставило. Когато едно правило „не слуша“, отговорът почти винаги е там, на две щраквания.
3.2 Всеки елемент е кутия
3.2.1 Четирите слоя на една кутия
За CSS всеки елемент е правоъгълна кутия. Тази кутия има четири вложени зони, отвътре навън:
- Съдържанието: текстът или дъщерните елементи.
- Вътрешният отстъп (padding): място между съдържанието и рамката. Част е от кутията и взима нейния фон.
- Рамката (border): линия около вътрешния отстъп.
- Външният отстъп (margin): място между тази рамка и съседната кутия. Прозрачен е и не взима фон.
И има един въпрос, който решава дали страницата ти е с размера, който си написал: когато напишеш width: 300px, тази ширина на съдържанието ли е, или на кутията с вътрешния отстъп и рамката? Историческият отговор, който продължава да е този, който браузърът прилага, ако никой не му каже друго, е на съдържанието. (Има изключения в стиловия лист на самия браузър: измерих го в Chrome 154 и бутоните, падащите списъци <select>, полетата за търсене и таблиците вече носят border-box; обикновено текстово поле или <div> — не.) Нарича се box-sizing: content-box. С нея вътрешният отстъп и рамката се добавят отвън:
<!-- fig03_05.html -->
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Cajas</title>
<style>
.box {
width: 300px;
padding: 20px;
border: 5px solid black;
margin: 10px;
background: #cfe2ff;
}
.border-box {
box-sizing: border-box;
}
</style>
</head>
<body>
<main>
<h1>Cajas</h1>
<div class="box">Caja con content-box (lo normal sin reglas)</div>
<div class="box border-box">Caja con border-box</div>
</main>
</body>
</html>
Cajas
Caja con content-box (lo normal sin reglas)
Caja con border-box
И двете кутии имат едно и също правило за ширина, width: 300px. Сметката на първата, с content-box: 300 съдържание, плюс 20 вътрешен отстъп от всяка страна (40), плюс 5 рамка от всяка страна (10): 350 пиксела обща ширина. Тази на втората, с box-sizing: border-box, ширината от 300 вече включва вътрешния отстъп и рамката, а съдържанието остава с онова, което е останало: 300 − 40 − 10 = 250. Измерих го в Chrome 154: първата е 350 пиксела широка, а втората 300. Изчисленото свойство width е 300px и в двете; променя се това, което значи.
Сега си представи реалния проблем. Имаш карта в колона от 500 пиксела и ѝ слагаш width: 100%, за да запълни колоната, 16 пиксела вътрешен отстъп, за да диша текстът, и рамка от 2 пиксела. С content-box картата е 500 съдържание + 32 вътрешен отстъп + 4 рамка = 536 пиксела: излиза от колоната си с 36 пиксела и се появява хоризонтална лента за превъртане. Начинаещият решава това чрез проба и грешка. Който знае за кутията, го решава с един ред.
Редът е box-sizing: border-box и е навикът на почти целия професионален CSS: с него ширината, която пишеш, е ширината, която виждаш, а вътрешният отстъп и рамката се изваждат от съдържанието отвътре. Пише се само веднъж, за всички елементи, и се появява в първия слой на таблото:
*,
*::before,
*::after {
box-sizing: border-box;
}
Три селектора, разделени със запетая: всички елементи и двата псевдоелемента, които CSS може да вмъкне преди и след съдържанието на всеки. Включват се, за да не избяга никоя генерирана кутия от критерия.
За да видиш кутията на какъвто и да е елемент, инспекторът има диаграма: в Chrome, в панела с изчислените стойности; във Firefox, в раздела „Оформление“. Това е правоъгълник вътре в друг с отбелязани четирите дебелини. Когато една кутия не е с размера, който очакваш, не изваждай пиксели: отвори диаграмата и виж коя от четирите зони е излишната.
3.2.2 Блок, ред и отстъпите, които се сливат
Има второ правило, което обяснява случаи, които объркват: не всички кутии се държат еднакво. Свойството display решава как се поставя една кутия спрямо съседките си. Три стойности обясняват повечето днешни случаи:
block: кутията заема цял ред. Приемаwidthиheightи четирите си външни отстъпа. Такива са абзаците, заглавията, секциите, таблиците.inline: кутията тече вътре в ред текст, като дума. Игнорираwidthиheightи вертикалните си външни отстъпи. Такива са<span>, връзките и таговете<strong>.inline-block: тече като дума, но позволява да се оразмерява като блок. Точно това ѝ трябва на една значка.
<!-- fig03_06.html -->
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Display</title>
<style>
.sample {
width: 200px;
height: 40px;
background: #cfe2ff;
}
.as-block { display: block; }
.as-inline { display: inline; }
.as-inline-block { display: inline-block; }
</style>
</head>
<body>
<main>
<h1>Display</h1>
<span class="sample as-block">block</span>
<span class="sample as-inline">inline</span>
<span class="sample as-inline-block">inline-block</span>
</main>
</body>
</html>
Display
block
inline inline-block
Трите <span> имат едно и също правило width: 200px; height: 40px;. Измерих го: block и inline-block са 200 × 40. inline е 36 × 18: ширината и височината му идват от текста „inline“, защото на кутия в ред размерите се игнорират. Ако някога напишеш width и „не прави нищо“, погледни първо display на елемента. <span class="status"> на таблото има нужда от вътрешен отстъп встрани и закръглени ъгли и затова се декларира display: inline-block.
Другата особеност на кутиите са външните отстъпи. Когато два блока са един върху друг и горният има долен външен отстъп, а долният — горен, разстоянието между тях не е сборът на двата: отстъпите се сливат и остава по-големият.
<!-- fig03_07.html -->
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Márgenes</title>
<style>
.first { margin: 0 0 20px; background: #cfe2ff; }
.second { margin: 30px 0 0; background: #d1e7dd; }
</style>
</head>
<body>
<main>
<h1>Márgenes</h1>
<p class="first">Primer párrafo: margen inferior de 20 px.</p>
<p class="second">Segundo párrafo: margen superior de 30 px.</p>
</main>
</body>
</html>
Márgenes
Primer párrafo: margen inferior de 20 px.
Segundo párrafo: margen superior de 30 px.
Ако отстъпите се сумираха, между двата абзаца щяха да има 50 пиксела. Измерих го: има 30. Нарича се сливане на външните отстъпи и става само между вертикалните отстъпи на съседни блокове (и между блок и първото му дете, когато няма вътрешен отстъп или рамка, които да ги разделят). Вътрешният отстъп никога не се слива, а елементите вътре във flex контейнер или решетка, които ще видиш в Урок 4, също не. Ако вертикалните разстояния не се събират, причината е тази. Навикът, който го избягва, е да пишеш отстъпите в една посока (например всички надолу) и да оставиш сливането да работи в твоя полза.
3.2.3 Единици: пиксели, rem и проценти
За да напишеш размер, трябва да избереш единица и изборът има последствия за достъпността, които не се виждат на екрана на този, който програмира.
pxе един CSS пиксел. Фиксирана мярка. Ползвай я за онова, което не бива да расте с текста: рамка от 1 px, сянка.remе размерът на буквите на корена на документа. По подразбиране браузърите го слагат на 16 px, така че1remса 16 px и2remса 32. Разликата сpxе тази, която има значение: читателят може да промени предпочитания си размер на буквите в настройките на браузъра и всичко, написано вrem, расте с него, докато написаното вpxне. Остава му мащабирането на страницата (Ctrl и +), което уголемява всичко, включителноpx, и което указанията приемат като начин за изпълнение; но който вече е фиксирал предпочитания си размер на буквите, за да чете във всички сайтове, вижда, че страницата ти го игнорира, и трябва всеки път да мащабира в нея. Затова таблото пише размерите на буквите и почти всички разстояния вrem. Това е критерият, който указанията за достъпност наричат преоразмеряване на текста (WCAG 1.4.4).%е процент от контейнера:width: 100%е „толкова широк, колкото родителят ми“.chе ширината на цифрата нула в текущия шрифт. Служи да ограничиш ширината на един текст: при повече от около 75 знака на ред окото се губи, когато скача на следващия.
Още един ред за височината на реда. Пише се line-height: 1.6, без единица: число само по себе си значи „1.6 пъти размера на буквите на всеки елемент“ и децата го наследяват като множител. С единица (1.6rem) се наследява като фиксирано количество и едро заглавие би останало със слепени редове.
3.3 Променливи: всяко решение, веднъж
3.3.1 Персонализирани свойства
Представи си, че синьото на таблото се появява в заглавието, във връзките, в бутона, в контура на фокуса. Това са четири места. В деня, в който някой каже „по-добре по-тъмно синьо“ или „клиентът иска зелено“, трябва да намериш четирите и да ги промениш, без да забравиш нито едно. CSS решава това с персонализирани свойства, които са известни като променливи.
Променливата е свойство, чието име измисляш ти и винаги започва с две тирета. Декларира се вътре в правило и се ползва с функцията var():
:root {
--color-accent: #0b5cad;
}
a {
color: var(--color-accent);
}
button {
background: var(--color-accent);
}
:root е псевдокласът, който избира корена на документа (елемента <html>) и се ползва като каноничното място за глобалните променливи. Променливите се наследяват, като цвета: променлива, декларирана в :root, е налична в целия документ, а такава, декларирана в един елемент, е налична в него и в потомците му. Това се ползва за трика, който движи значките на таблото и който ще видиш след малко.
Има една дисциплина на имената, която струва повече от всеки друг съвет: името казва за какво служи, не как изглежда. --color-accent е добро име; --blue е капан, защото в деня, в който акцентът стане зелен, променливата --blue ще съдържа зелено и никой няма да разбере кода. Същото с --color-down-text за текста на паднало състояние или --space-3 за разстояние. Една променлива е решение с име.
И три променливи, които заслужават отделно обяснение, са тези на значката. Обърни внимание как се решава цветът на двете значки, зелената на „Disponible“ (налична) и червената на „Caído“ (паднала), с едно-единствено правило:
.status {
background: var(--badge-bg);
color: var(--badge-text);
}
.status-available {
--badge-bg: var(--color-available-bg);
--badge-text: var(--color-available-text);
}
.status-down {
--badge-bg: var(--color-down-bg);
--badge-text: var(--color-down-text);
}
Правилото .status знае как се чертае една значка, но не в какъв цвят; двата следващи класа казват само в какъв цвят, като променят две променливи. Благодарение на наследяването на променливите .status ги вижда. Този начин на писане има предимство, което скоро ще забележиш: добавянето на трето състояние в бъдеще е добавяне на клас от три реда, без да се пипа правилото на значката. Една бележка: значка, която има само класа status и никой от другите два, остава без цвят на фона, защото променливите, които ползва, не съществуват. Това е правилното поведение и ще се види в раздела за грешките.
3.3.2 Типография и пространство
Променливите не са само за цветове. Таблото декларира своята скала на разстоянията (--space-1 до --space-5, от четвърт rem до два и половина) и своето семейство шрифтове:
--font-body: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
Един списък от семейства се чете отляво надясно като „ползвай първото, което съществува, а ако не, следващото“. system-ui иска шрифта, с който операционната система изчертава собствените си менюта, който е най-четливият на всяка платформа и не се изтегля отникъде. Останалите са имената на системния шрифт на всяка платформа, в случай че браузърът не разбира system-ui, а последното, sans-serif, е генерично семейство, което е мрежата за безопасност. Не е нужно да изтегляш никакъв шрифт, за да започнеш.
Скалата на разстоянията има цел, която не е естетическа. Когато всяко разстояние на таблото идва от същите пет мерки, страницата има ритъм, а когато някой иска повече въздух, променя една мярка и всичко се нагласява. Височините на реда (1.6 за тялото и 1.2 за заглавията, които са къси) затварят системата.
Едно малко свойство, което прави разлика в таблица с числа: font-variant-numeric: tabular-nums. С него всички цифри заемат една и съща ширина, така че 120 ms и 950 ms се подравняват цифра по цифра. Това прави колона с времена сравнима с един поглед. Таблото го прилага върху колоната с времената заедно с text-align: right.
3.3.3 Контраст и фокус: онова, което се мери
Указанията за достъпност (WCAG 2.2) изискват нормалният текст да има коефициент на контраст поне 4.5 към 1 спрямо фона си (критерий 1.4.3), а компонентите на интерфейса и индикаторът за фокус да достигат 3 към 1 (1.4.11). Коефициентът се изчислява с формула върху яркостта на двата цвята и не е нужно да я учиш: инструментът за избор на цвят в инструментите на браузъра я изчислява, а има и онлайн проверители като този на WebAIM. Което ти трябва, е навикът да мериш, преди да избереш. Ето числата за цветовете на таблото, които изчислих с формулата на W3C:
| Комбинация | Контраст | Минимум |
|---|---|---|
| Основен текст върху бял фон | 16.56 : 1 | 4.5 |
Приглушен текст (--color-muted) върху бяло |
6.39 : 1 | 4.5 |
| Приглушен текст върху фона на страницата | 6.00 : 1 | 4.5 |
| Връзки и бутон върху бяло | 6.67 : 1 | 4.5 |
| Бели букви на бутона върху акцента | 6.67 : 1 | 4.5 |
| Значка „Disponible“ | 7.21 : 1 | 4.5 |
| Значка „Caído“ | 7.08 : 1 | 4.5 |
| Рамка на полето за търсене върху бяло | 4.55 : 1 | 3 |
| Пръстен на фокуса върху бяло | 6.67 : 1 | 3 |
Едно число е решение, което може да се преглежда. „Изглежда добре“ — не.
Има втори критерий за цвета и той е, че цветът не може да бъде единственият начин да се съобщи нещо (WCAG 1.4.1, „Използване на цвят“). Червената значка „Caído“ се различава от зелената за този, който вижда цветове, но човек с червено-зелен далтонизъм може да не различи тези двете. Затова значката носи освен това текст („Caído“): казва го с думи, а цветът само подсилва. Това решение вече си го взел в Урок 2, като написа „Caído“ вътре в <span>. CSS го подобрява, без да го променя.
И фокусът. В предишния урок направи теста с клавиша Tab; но браузърът ти показваше фокуса със своя пръстен по подразбиране. Често в стиловия лист на начинаещ се намира outline: none, за „да се махне този грозен контур“. Това е грешка с цена: който навигира с клавиатурата, остава, без да знае къде е. Критерият на указанията е фокусът да е видим (WCAG 2.4.7). Правилният инструмент е псевдокласът :focus-visible, който избира елемент на фокус когато браузърът смята, че фокусът трябва да се показва: с клавиатурата да, а при щракване с мишката върху бутон, обикновено не. Така можеш да дадеш собствен контур, видим и твърд, без да пречи на този, който ползва мишка. Широко достъпен е в браузърите от март 2022 г.
Решен пример: четливото табло
Вече имаш трите идеи. Това е стиловият лист на цялото табло; мини през него по слоеве, както е написана.
/* fig03_08/styles.css */
/* El orden de las capas se declara una sola vez, arriba. La última gana. */
@layer reset, base, components;
@layer reset {
/* La caja: el ancho que escribes es el ancho que se ve. */
*,
*::before,
*::after {
box-sizing: border-box;
}
/* El navegador no hace que los campos hereden la letra del texto. */
input,
button {
font: inherit;
}
}
@layer base {
/* Las decisiones del panel viven aquí, con nombres que dicen para qué sirven. */
:root {
--color-text: #1b1f24;
--color-muted: #57606a;
--color-page: #f6f8fa;
--color-surface: #ffffff;
--color-border: #d0d7de;
--color-control: #6e7781;
--color-accent: #0b5cad;
--color-available-text: #0f5132;
--color-available-bg: #d1e7dd;
--color-down-text: #842029;
--color-down-bg: #f8d7da;
--space-1: 0.25rem;
--space-2: 0.5rem;
--space-3: 1rem;
--space-4: 1.5rem;
--space-5: 2.5rem;
--font-body: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
--line-height: 1.6;
--radius: 0.375rem;
}
body {
margin: 0;
background: var(--color-page);
color: var(--color-text);
font-family: var(--font-body);
line-height: var(--line-height);
}
/* Un ancho cómodo para leer, centrado: auto reparte el espacio sobrante. */
header,
main,
footer {
max-width: 60rem;
margin: 0 auto;
padding: var(--space-3);
}
h1 {
margin: 0 0 var(--space-2);
font-size: 2rem;
line-height: 1.2;
}
h2 {
margin: 0 0 var(--space-3);
font-size: 1.375rem;
line-height: 1.3;
}
a {
color: var(--color-accent);
}
/* Que el foco del teclado se vea siempre: nunca outline: none sin reemplazo. */
:focus-visible {
outline: 3px solid var(--color-accent);
outline-offset: 2px;
}
footer {
color: var(--color-muted);
}
}
@layer components {
/* Cada sección es una hoja blanca sobre el fondo de la página. */
section {
margin-bottom: var(--space-4);
padding: var(--space-3) var(--space-4);
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: var(--radius);
}
/* El resumen: cada par nombre-valor en su renglón. */
dl div {
padding: var(--space-2) 0;
border-bottom: 1px solid var(--color-border);
}
dl div:last-child {
border-bottom: 0;
}
dt {
color: var(--color-muted);
}
dd {
margin: 0;
font-size: 1.25rem;
font-weight: 600;
}
/* Los controles. */
label {
margin-right: var(--space-3);
}
input[type="search"] {
min-height: 2.5rem;
padding: var(--space-1) var(--space-2);
border: 1px solid var(--color-control);
border-radius: var(--radius);
}
fieldset {
margin: var(--space-3) 0;
padding: var(--space-2) var(--space-3);
border: 1px solid var(--color-border);
border-radius: var(--radius);
}
button {
min-height: 2.5rem;
padding: var(--space-1) var(--space-3);
border: 0;
border-radius: var(--radius);
background: var(--color-accent);
color: var(--color-surface);
font-weight: 600;
cursor: pointer;
}
button:hover {
background: var(--color-text);
}
/* La tabla. */
table {
width: 100%;
margin-top: var(--space-3);
border-collapse: collapse;
}
caption {
padding-bottom: var(--space-2);
color: var(--color-muted);
text-align: left;
}
th,
td {
padding: var(--space-2) var(--space-3);
border-bottom: 1px solid var(--color-border);
text-align: left;
}
/* Los números a la derecha y con cifras del mismo ancho para que alineen. */
th:last-child,
td:last-child {
text-align: right;
font-variant-numeric: tabular-nums;
}
/* La insignia de estado: una sola regla, y las variantes solo cambian variables. */
.status {
display: inline-block;
padding: 0 var(--space-2);
border-radius: 999px;
background: var(--badge-bg);
color: var(--badge-text);
font-size: 0.875rem;
font-weight: 600;
}
.status-available {
--badge-bg: var(--color-available-bg);
--badge-text: var(--color-available-text);
}
.status-down {
--badge-bg: var(--color-down-bg);
--badge-text: var(--color-down-text);
}
}
А страницата е тази от Урок 2 с един-единствен нов ред в <head>: връзката към стиловия лист. В хранилището той живее в fig03_08/styles.css; в твоя проект го запази като css/styles.css, вътре в папката css, която създаде в Упражнение 1 на Урок 1, и напиши href="css/styles.css".
<!-- fig03_08.html -->
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Revisor de servicios</title>
<link rel="icon" href="data:,">
<link rel="stylesheet" href="fig03_08/styles.css">
</head>
<body>
<header>
<h1>Revisor de servicios</h1>
<p>Última revisión:
<time datetime="2026-10-07T10:30:00-06:00">7 de octubre de 2026, 10:30</time>
</p>
<nav>
<p>Ir a: <a href="#summary">Resumen</a> o <a href="#services">Servicios</a></p>
</nav>
</header>
<main>
<section id="summary">
<h2>Resumen</h2>
<dl>
<div>
<dt>Servicios revisados</dt>
<dd>5</dd>
</div>
<div>
<dt>Disponibles</dt>
<dd>4 de 5</dd>
</div>
<div>
<dt>Caídos</dt>
<dd>1</dd>
</div>
<div>
<dt>Respuesta promedio</dt>
<dd>465 ms</dd>
</div>
</dl>
</section>
<section id="services">
<h2>Servicios</h2>
<p>
<label for="search">Buscar servicio</label>
<input type="search" id="search" name="search">
</p>
<fieldset>
<legend>Mostrar</legend>
<label><input type="radio" name="filter" value="all" checked> Todos</label>
<label><input type="radio" name="filter" value="available"> Disponibles</label>
<label><input type="radio" name="filter" value="down"> Caídos</label>
</fieldset>
<p><button type="button">Revisar ahora</button></p>
<table>
<caption>Estado de los servicios en la última revisión</caption>
<thead>
<tr>
<th scope="col">Servicio</th>
<th scope="col">Estado</th>
<th scope="col">Tiempo de respuesta</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Catálogo</th>
<td><span class="status status-available">Disponible</span></td>
<td>120 ms</td>
</tr>
<tr>
<th scope="row">Pagos</th>
<td><span class="status status-available">Disponible</span></td>
<td>480 ms</td>
</tr>
<tr>
<th scope="row">Inventario</th>
<td><span class="status status-down">Caído</span></td>
<td>sin respuesta</td>
</tr>
<tr>
<th scope="row">Notificaciones</th>
<td><span class="status status-available">Disponible</span></td>
<td>310 ms</td>
</tr>
<tr>
<th scope="row">Búsqueda</th>
<td><span class="status status-available">Disponible</span></td>
<td>950 ms</td>
</tr>
</tbody>
</table>
</section>
</main>
<footer>
<p>Datos de ejemplo escritos a mano.</p>
</footer>
</body>
</html>
Revisor de servicios
Última revisión: 7 de octubre de 2026, 10:30
Ir a: Resumen o Servicios
Resumen
Servicios revisados
5
Disponibles
4 de 5
Caídos
1
Respuesta promedio
465 ms
Servicios
Buscar servicio
Mostrar
Todos Disponibles Caídos
Revisar ahora
Estado de los servicios en la última revisión
Servicio Estado Tiempo de respuesta
Catálogo Disponible 120 ms
Pagos Disponible 480 ms
Inventario Caído sin respuesta
Notificaciones Disponible 310 ms
Búsqueda Disponible 950 ms
Datos de ejemplo escritos a mano.
Текстът е същият като в Урок 2 и това е правилното: CSS не променя онова, което страницата казва, а само как се изчертава. Отвори страницата на локалния си сървър и забележи какво наистина се е променило. Решенията, едно по едно:
- Три слоя по ред:
reset(кутията и шрифтовете на контролите),base(променливи, тяло, заглавия, връзки, фокус) иcomponents(секции, обобщение, контроли, таблица, значки). Тъй като всяко правило живее в слой и между слоевете командва редът, няма нито един!importantи не е нужно никой от селекторите да е особено специфичен. Ако утре някой напише правило без слой, то печели пред всичко: това е аварийната врата и затова се пише с намерение. - Всички решения, в
:root: цветове, разстояния, шрифт. Останалата част от стиловия лист не съдържа нито един цвят, записан в шестнайсетичен вид; всичко идва отvar(). За да смениш синьото на цялото табло, се променя един ред. Това е упражнение 3. margin: 0 autoсmax-width: 60rem: съдържанието има удобна максимална ширина, аautoв страничните външни отстъпи разпределя оставащото място на двете страни. Това е начинът да центрираш блок с ширина.:focus-visible: пръстен от 3 пиксела в цвета на акцента, отделен на 2 пиксела от елемента. Натисни Tab: пръстенът се появява върху връзката, полето, групата радио бутони и бутона. Проверих го в Chrome: бутонът на фокус с клавиатурата има контурsolidс3pxи цвятrgb(11, 92, 173).- Таблицата:
border-collapse: collapseслива рамките на клетките в една линия (по подразбиране всяка клетка чертае своите, с пролука между тях). Клетките носят вътрешен отстъп, за да дишат, а линиите се ограничават до долната част на всеки ред. - Числата:
th:last-child, td:last-childизбира последната клетка на всеки ред, която е тази с времената, и я подравнява вдясно с табличен вид на цифрите. Обърни внимание, че<th>на името на реда не е засегнат: той е първото дете на своя ред, не последното. - Значките: едно правило, два варианта и текстът си остава там: цветът само подсилва онова, което казва думата.
- Бутонът: е висок поне
2.5rem(40 пиксела при шрифт 16), над минимума от 24 × 24, който указанията изискват за докосваните цели (WCAG 2.5.8). Полето за търсене също е 40.
И една честна сметка, преди да затворим. При браузър широк 1000 пиксела таблото изглежда добре. Ако го смалиш до 320 пиксела, което е ширината на малък телефон, се появява хоризонтална лента за превъртане: измерих, че документът е 414 пиксела широк в прозорец от 320. Таблицата с трите си колони не се събира. Не е пропуск на този урок: това е темата на следващия. Днес таблото е четливо; в уроци 4 и 5 се подрежда и става адаптивно.
Грешката, която ще видиш
Браузърът не показва грешки в CSS. Ако напишеш погрешно свойство или стойност, просто игнорира декларацията и продължава. Това прави грешките в CSS тихи и затова обикновено се търсят там, където се проявяват („защо заглавието ми не е синьо?“), а не там, където са се родили. За да ги видиш, има два инструмента: CSS валидаторът на W3C и инспекторът на браузъра, където невалидната декларация се появява зачеркната или маркирана.
Тази страница има четири синтактични грешки и една погрешно написана променлива:
<!-- fig03_09.html -->
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Estilos que no hacen caso</title>
<style>
h1 {
colour: navy;
}
p {
color: #12;
}
table {
width: 100 px;
}
.status {
padding: 0.25rem 0.5rem
border-radius: 999px;
}
.note {
color: var(--color-textt);
}
</style>
</head>
<body>
<main>
<h1>Estilos que no hacen caso</h1>
<p>Ninguna de estas reglas hace lo que el autor quería.</p>
<p class="note">Este párrafo usa una variable que no existe.</p>
</main>
</body>
</html>
CSS валидаторът на W3C живее на https://jigsaw.w3.org/css-validator/ и приема файл, адрес или поставен текст. Ето какво отговаря за блока със стилове по-горе (копирано от истинския резултат):
Line : 3 h1
Property “colour” doesn't exist. The closest matching property name is “color” :
navy
Line : 6 p
(Value Error : color (nullcolors.html#propdef-color))
“#12” is not a valid color 3 or 6 hexadecimals numbers :
Line : 9 table
(Value Error : width (nullvisudet.html#propdef-width))
Too many values or values are not recognized :
Line : 13 .status
(Value Error : padding (nullbox.html#propdef-padding))
Missing a semicolon before the property name “border-radius”
Всяко съобщение носи реда от файла и селектора, където се случва. Едно по едно:
colourне съществува. Британското правописание на „color“. И съобщението ти казва кое е свойството, което си искал да напишеш. Браузърът игнорира декларацията и заглавието остана черно; нямаше предупреждение.#12не е цвят. Шестнайсетичният цвят има 3 или 6 цифри (и 4 или 8, ако носи прозрачност). Стойността е отхвърлена.100 pxима интервал. За CSS това са две стойности, а ширината приема само една: пише се100px, слято.- Липсва точка и запетая в края на декларацията на
padding. Браузърът е прочел0.25rem 0.5rem border-radius: 999pxкато една-единствена невалидна стойност и е загубил двете декларации. Това е най-честата грешка и тази, която най-много изненадва, защото един зле затворен ред отнася със себе си и този отдолу.
Грешката с променливата е друга: var(--color-textt) има излишно t и променливата не съществува. Валидаторът не я открива (предупреждението му за таблото казва, че поради динамичната им природа променливите не се проверяват статично). Нито браузърът протестира: измерих го в Chrome и абзацът остана с обичайния си черен цвят, на наследения текст. Променлива, която не съществува, прави декларацията невалидна в момента на изчисляване на стойността и свойството се връща към наследената или началната си стойност, безшумно. Начинът да я намериш е този, който вече познаваш: в инспектора декларацията се появява маркирана, а изчислената стойност на абзаца не е тази, която очакваш. Друг добър навик: една променлива може да носи резервна стойност като втори аргумент, var(--color-text, black), която се ползва, когато променливата не съществува.
Една последна проверка върху таблото от тази глава: прекарах стиловия му лист през същия валидатор и той отговори „Congratulations! No Error Found“, само с две предупреждения, които казват, че променливите не се проверяват статично.
Какво се прави погрешно
1. Да решаваш конфликт, като вдигаш силата. Повече селектори, идентификатор, !important. Цена: всяка кръпка изисква още по-силна и стиловият лист накрая става невъзможен за промяна. Поправка: изчисли специфичността на двете правила, свали това, което не трябва да е силно, и подреждай със слоеве.
2. Да ползваш идентификатори за стил. #summary dd { ... }. Цена: идентификаторът тежи (1,0,0), което побеждава хиляда класа, и единственият начин да го победиш е друг идентификатор. Поправка: стилът върви с класове или със селектори за елемент; id остава за вътрешните връзки и за етикетите.
3. Атрибутът style в HTML. Цена: печели пред почти всичко и смесва външния вид със съдържанието; за да смениш един цвят, трябва да редактираш HTML на сто места. Поправка: клас.
4. outline: none без замяна. Цена: потребителят с клавиатура остава, без да знае къде е. Поправка: :focus-visible със собствен контур.
5. Размери на буквите в px. Цена: който увеличава размера на буквите от настройките на браузъра, не вижда ефект и му остава само да мащабира страницата ти всеки път. Поправка: rem.
6. Да съобщаваш състоянието само с цвят. Значка, която е само червена или зелена. Цена: губи се за този, който не различава тези цветове, и за този, който ползва екранен четец. Поправка: думата винаги; цветът — като подсилване.
7. Да изваждаш пиксели на око. width: 280px, за да „се побере“ с вътрешния отстъп. Цена: числото зависи от сметка, която никой не е написал, и се чупи, щом промениш вътрешния отстъп. Поправка: box-sizing: border-box за всички и ширината, която наистина искаш.
8. Да наименуваш променливите по цвета им. --blue, --red. Цена: в деня, в който решението се промени, името лъже. Поправка: имена по функция, като --color-accent.
9. Да копираш разпилени стойности във всяко правило. Същото #0b5cad на пет места. Цена: да го смениш е търсене и замяна и винаги се забравя едно. Поправка: променлива.
Упражнения
Упражнение 1 — Предскажи, преди да отвориш
За тази страница запиши какъв цвят ще е всеки елемент от списъка (A, B и C) преди да я отвориш. После я отвори и сравни. Накрая промени само едно нещо в едно правило, така че B да изглежда зелен, а C — аленочервен, без да ползваш !important:
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Predice antes de abrir</title>
<style>
li { color: black; }
#list li { color: navy; }
.done { color: green; }
li.urgent { color: crimson; }
</style>
</head>
<body>
<ul id="list">
<li>A</li>
<li class="done">B</li>
<li class="done urgent">C</li>
</ul>
</body>
</html>
Упражнение 2 — Картата, която излиза
Една карта има width: 100%, padding: 16px и border: 2px solid black, в колона широка 500 px. Изчисли на ръка колко е голяма картата с content-box и колко с border-box. После напиши страницата, измери я с инспектора и провери сметката си.
Упражнение 3 — Смени акцента с един ред
Смени цвета на акцента на таблото на друг по твой избор, като редактираш само един ред от стиловия лист. Преди да го избереш, провери с инструмента за избор на цвят на инструментите на браузъра или с проверителя на WebAIM, че отговаря на 4.5 : 1 върху бяло. След това отговори: кои неща на таблото смениха цвета си с този един ред?
Упражнение 4 — Развържи война на !important
Този стилов лист има един !important, който пречи значката „Caído“ да изглежда червена. Оправи го, като го махнеш и без да добавяш друг, и без да променяш HTML:
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Guerra de important</title>
<style>
.status { color: gray !important; padding: 0 8px; }
.status-down { color: red; }
</style>
</head>
<body>
<p><span class="status status-down">Caído</span></p>
</body>
</html>
Решения
Решение 1
И трите са сини (navy, rgb(0, 0, 128)). Правилото #list li има специфичност (1,0,1), с идентификатор, и побеждава всички останали: .done е (0,1,0), а li.urgent е (0,1,1) и никое не стига до идентификатор. Това е капанът на ползването на идентификатори за стил: щом се появи един, никой клас не може да се състезава.
За да бъде B зелен, а C аленочервен, трябва да свалиш силата на това правило, не да вдигаш тази на другите. :where() струва нула, така че :where(#list) li има специфичност (0,0,1):
li { color: black; }
:where(#list) li { color: navy; }
.done { color: green; }
li.urgent { color: crimson; }
С тази промяна (измерих го в Chrome 154) A е син (rgb(0, 0, 128)), B зелен (rgb(0, 128, 0)) и C аленочервен (rgb(220, 20, 60)). A си остава син, защото :where(#list) li и li са равни на (0,0,1) и печели това, което е по-надолу.
Решение 2
С content-box: 500 ширина на съдържанието + 32 вътрешен отстъп (16 + 16) + 4 рамка (2 + 2) = 536 пиксела, които излизат с 36 пиксела от колоната от 500. С border-box: ширината от 100% (500) вече включва всичко, така че е 500. Моето измерване съвпадна: 536 и 500.
.wrap { width: 500px; }
.card { width: 100%; padding: 16px; border: 2px solid black; }
.card.fixed { box-sizing: border-box; }
Решение 3
Трябва да смениш реда --color-accent вътре в :root. Например #6f2da8 (лилаво) дава 8.03 : 1 върху бяло, което отговаря с голям запас. Цвят като #9a4dff изглежда жив и не отговаря: 4.30 : 1, под 4.5. С този единствен ред сменят цвета си: връзките, фонът на бутона, контурът на фокуса и, покрай това, текстът на бутона, който е бял (--color-surface) и също има нужда от достатъчен контраст спрямо новия акцент. Това е предимството да наименуваш решението, а не цвета: четири места, един ред. И урокът, който го придружава: когато променяш една променлива, трябва да прегледаш всички комбинации, в които участва, не само тази, която си имал предвид.
Решение 4
!important кара .status да печели пред всичко. Като го махнеш, двете правила имат една и съща специфичност (0,1,0) и решава редът: .status-down е след него, така че печели и текстът е червен. Една корекция, която освен това прави стиловия лист по-солиден, е да се ползва шаблонът с променливи от раздел 3.3.1:
.status { color: var(--badge-text); padding: 0 8px; }
.status-down { --badge-text: red; }
С този шаблон не е възможен конфликт: правилото .status никога не казва цвят, само чете променлива. А който има нужда от друг вариант, добавя клас от един ред. Преди да го ползваш, измери дали red върху реалния фон отговаря на контраста.
Как разбирам, че съм успял
- Таблото се отваря на
http://localhost:8000/с приложения стилов лист, без грешки в конзолата на инструментите на браузъра. - CSS валидаторът (
https://jigsaw.w3.org/css-validator/) отговаря „Congratulations! No Error Found“ върху твояstyles.css. - При натискане на Tab виждаш син пръстен от 3 пиксела около връзката, полето, радио бутоните и бутона, а търсенето на
outline: noneв стиловия ти лист не дава резултат. - В инспектора, върху кутията с
border-boxнаfig03_05.html, диаграмата на кутията показва съдържание с ширина 250 пиксела (300 минус 40 вътрешен отстъп и 10 рамка), а върху другата — 300. - Можеш да предвидиш цвета на четирите абзаца на
fig03_02.html, преди да я отвориш, като казваш коя стъпка от каскадата решава за всеки. - Всеки цвят на текст в стиловия ти лист отговаря на 4.5 : 1 спрямо фона си и можеш да кажеш с какъв инструмент си го измерил.
- Промяната на стойността на
--color-accentпроменя едновременно връзките, бутона и пръстена на фокуса.
За допълнително четене
- MDN, „Handling conflicts“ (каскада, специфичност и наследяване) — учебната статия на Mozilla по същата тема, с практически задачи. На английски. Посетено на 7 октомври 2026 г.
- W3C, „CSS Cascading and Inheritance Level 5“ — спецификацията, където е записан алгоритъмът на каскадата, с нейните слоеве и ред. На английски. Посетено на 7 октомври 2026 г.
- MDN, „The box model“ — кутията, вътрешният отстъп, рамката и външният отстъп с диаграми и
box-sizing. На английски. Посетено на 7 октомври 2026 г. - web.dev, „Learn CSS“ — безплатен курс на Google по теми, полезен като подреден справочник за селектори, кутия, каскада и наследяване. На английски. Посетено на 7 октомври 2026 г.
Предпочитате имейл? Пишете ни на hola@habil.mx