CSS для Markdown-контенту: розумні налаштування, що не ламають продакшн

Було корисно?

Ви публікуєте сторінку з документацією. Хтось вставляє таблицю в Markdown з восьми колонок, блок коду з рядком у 300 символів і вкладений список, що нагадує римський перепис. Раптом макет «стрибає», на мобільному з’являються горизонтальні смуги прокрутки, а команда дизайн-системи приходить із баг-репортом, ніби то доказом злочину.

Markdown здається «простим», доки ви не почнете використовувати його в продакшні масштабно: різні рендерери, невідомий вміст, непередбачуваний HTML і CSS, що неконтрольовано протікає по всьому сайту. Давайте задамо розумні налаштування — для заголовків, списків, таблиць, коду, цитат — щоб ваш контент залишався читабельним, а UI — цілим.

Принципи для продакшну: область, передбачуваність і неприємні крайні випадки

Стилізація Markdown — це не «підібрати приємний шрифт». Це ізоляція контенту з типографією. Ви берете напівструктурований вхід і рендерите його в продукті з власними припущеннями про відступи, кольори й макет. Насправді ви захищаєте решту сторінки від контенту і захищаєте контент від решти CSS.

Три принципи, що вбережуть вас від проблем:

  • Скруйте все в область. Помістіть Markdown у контейнер, наприклад .md, і стилізуйте тільки всередині нього. Якщо ви публікуєте глобальні правила для h1, pre або table, ви не «стилізуєте Markdown» — ви ризикуєте всім сайтом.
  • Віддавайте перевагу передбачуваному ритму над хитромудрими візуальними ефектами. Користувачі читають документацію під стресом: під час інцидентів, міграцій чи онбордингу. Ваш CSS має бути нудним у кращому сенсі: стабільні відступи, читабельна довжина рядка, розумне форматування коду.
  • Проєктуйте для найгіршого вхідного вмісту. Довгі URL, нерозривні хеші, глибоко вкладені списки, широкі таблиці та блоки коду, що відмовляються переноситись. Якщо ви стилізуєте лише «щасливий шлях», перший просунутий користувач зробить QA за вас.

Ще одне: змушуйте браузер робити менше. Химерний CSS, що викликає перерахунок макета, зсуви або велике перемальовування — це причина, чому «сторінка документації» стає «інцидентом продуктивності». Справді.

Парафраз ідеї Вернера Фогельса (reliability engineering): будувати системи з припущенням про відмову і робити відмови переживаними. Це стосується й Markdown-контенту.

Жарт №1: CSS — єдина мова, де ви можете помилитися сімнадцятьма різними способами і все одно випустити сторінку, що «виглядає нормально» на вашому ноутбуку.

Факти та історичний контекст, що пояснюють сучасний безлад

Markdown здається позачасовим, але екосистема навколо нього — це латка з різних рішень. Кілька коротких фактів допоможуть пояснити, чому «стилізацію Markdown» не можна трактувати як один окремий напрямок:

  1. Markdown з’явився як зручний формат, а не як стандарт публікації. Оригінальний Markdown Джона Грубера (2004) був задуманий для швидкого запису в HTML, а не для послідовного семантичного виходу між реалізаціями.
  2. CommonMark виник тому, що «Markdown» був занадто неоднозначним. Різні парсери по-різному трактували крайові випадки (наприклад, підкреслення в словах, вкладені списки), тому HTML-вихід може відрізнятися в залежності від рендерера.
  3. GitHub Flavored Markdown популяризував таблиці та task-листи. Ці можливості не були в оригінальному специфікації, але стали «очікуваними», тож ваш CSS має їх обробляти.
  4. Рання веб-типографіка передбачала короткі рядки і низьку щільність. Багато значень за замовчуванням походять з епохи екранів 800×600. Сьогодні й великі монітори, й крихітні телефони впливають на ваш контент.
  5. Бібліотеки підсвітки коду сформували очікування для CSS. Інструменти як highlight.js і Prism додали багато класів (span-суп), всередині pre, що впливає на висоту рядка та виділення.
  6. CSS-ресети нормалізували поведінку браузерів — а потім створили нові конфлікти. Ресет може обнулити margin у списків і заголовків, через що Markdown-контент виглядатиме як стіна тексту, якщо не відновити ритм.
  7. Таблиці за замовчуванням не дружні до мобільних пристроїв. HTML-таблиці з’явилися задовго до responsive-дизайну; обгортання з горизонтальною прокруткою стало ухиленням за замовчуванням.
  8. Темна тема — це нині вимога, а не опція. Сторінка документації з білими блоками коду в темній темі читається як ліхтарик. Потрібні продумані кольори.

Базовий набір CSS, який можна розгортати

Якщо взяти одне з цього матеріалу: використовуйте клас-контейнер, визначте типографічний ритм і оброблюйте переповнення. CSS у <style> цього документа — саме це: scoped, нудний і стійкий до типових проблем вмісту.

Ось чому базовий набір структуровано так:

  • CSS-змінні дають панель налаштувань: змініть стек шрифтів, кольори, відступи та максимальну довжину рядка без шукання по селекторах.
  • Максимальна ширина в символах (78ch) зберігає довжину рядка читабельною на великих моніторах. Також це не дозволяє таблицям і блокам коду перетворюватися на руйнівні елементи макета.
  • Scroll-margin на заголовках запобігає приховуванню анкерів під липкими заголовками. Ви будете вдячні за це, коли виконаєте runbook з анкорами.
  • Клас-обгортка для таблиць (.table-wrap) дозволяє обмежити горизонтальне переповнення без того, щоб вся сторінка прокручувалася вбік.
  • Друковані стилі не дозволяють темним блокам коду марнувати тонер і ставати нечитаемими.

Чого не слід робити: копіювати стилі «теми блогу» з складними grid-розкладками, плаваючими викликами й декоративними псевдо-елементами. Markdown — універсальний адаптер; ваш CSS має бути як захист від стрибків напруги.

Заголовки: ієрархія без драм

Заголовки в Markdown обманливо дорогі. Вони впливають на навігацію, анкорні посилання, читабельність і сприйняття довіри до документа. Якщо заголовки виглядають непослідовно, уміст здається непослідовним — навіть якщо він правильний.

Правила відступів, що запобігають «акордеонним» документам

Механіка невдачі: заголовки з великими верхніми відступами створюють ефект «акордеону», коли контент здається розірваним і користувачі втрачають контекст. Навпаки — відсутність відступів робить сторінку схожою на лог-файл.

Використовуйте послідовний каденс:

  • h2 отримує більший верхній відступ, щоб чітко відокремлювати розділи.
  • h3 отримує менший верхній відступ, щоб підрозділи не виглядали як окремі глави.
  • Підтримуйте меншу висоту рядка для заголовків порівняно з текстом, щоб уникнути незграбних багаторядкових заголовків.

Анкори і липкі заголовки

Якщо у вас є липкий заголовок, стандартна поведінка браузера при переході за анкером прокрутить заголовок під нього. Користувачі клікають посилання — сторінка стрибає, а заголовок зникає. Це не просто дратує — виглядає зламаним.

Виправте це за допомогою scroll-margin-top на заголовках. Це просто, ефективно і не вимагає JavaScript. Використовуйте значення, що відповідає висоті вашого заголовка плюс невеликий запас.

Не стилізуйте заголовки глобально

Якщо ваш Markdown-рендерер живе в ширшому додатку, глобальні стилі заголовків — це шлях до випадкового перестилювання заголовків модалів, карток і навігації. Завжди scope-те.

Списки: відступи, вкладення і чому маркери брешуть

Списки — це місце, куди Markdown йде, щоб стати дивним. Вкладені списки, змішані нумеровані/маркерні списки, task-листи й елементи списку з абзацами можуть призводити до різного маркування в залежності від рендерера.

Відступи, що виживають при вкладеному вмісті

За замовчуванням відступи списків різняться в браузерах. Ресети часто знищують padding у списків. Результат: маркери притискаються до краю або вкладені списки виглядають як хтось кинув сходи посеред сторінки.

Практичні правила:

  • Використовуйте padding-left на ul/ol, а не покладайтеся на маркери за замовчуванням.
  • Дайте li невеликий вертикальний відступ. Не повний 1rem, інакше список перетвориться на буклет.
  • Контролюйте відступи для вкладених списків, щоб вони не роздувалися.

Task-листи і чекбокси

Деякі рендерери виводять task-листи як input type="checkbox" всередині li. Якщо ви стилізуєте форми глобально, ви можете ненавмисно змінити їхній розмір або вигляд. Тримайте стилі для елементів форми в Markdown консервативними або ж scope-те їх щільно.

Жарт №2: вкладені списки як орг‑структури — технічно коректні, емоційно виснажливі.

Таблиці: overflow, вирівнювання і «сюрприз з восьми колонок»

Таблиці — головна причина скарг «чому ця сторінка прокручується по горизонталі на iPhone?». Браузер радо розширює viewport, щоб вмістити таблицю, і ваш макет починає танцювати.

Обгорніть таблиці, не боріться з ними

У вас є дві реалістичні стратегії:

  1. Дозволити горизонтальну прокрутку. Покладіть таблицю в wrapper з overflow-x: auto. Це найменш поганий варіант для широких даних.
  2. Перетворювати таблиці в картки на малих екранах. Це може бути приємно, але складно й крихко для довільних Markdown-таблиць. Робіть це лише якщо контролюєте схему таблиць.

Для більшості Markdown-контенту в дикій природі використовуйте wrapper. Це чесно: широкі дані — широкі дані.

Стабільність: уникайте зсувів макета при завантаженні таблиць

Таблиці можуть викликати зсув макета, коли шрифти завантажуються пізно або коли початковий рендер відбувається без стилів обгортки. Тримайте критичний CSS вбудованим або завантаженим рано, і робіть стилі таблиць простими.

Вирівнювання чисел

У документації часто бувають номери версій, розміри й значення затримки. Розгляньте font-variant-numeric: tabular-nums для таблиць, щоб зменшити «тремтіння». Це не вирішує все, але покращує швидке сканування, якщо ваш шрифт це підтримує.

Кодові блоки та inline-код: читабельно, придатно для копіювання і не UX-вимагання

Код — сенс більшості технічних Markdown. Якщо стилі коду неправильні, документація неправильна. Точка.

Inline-код: читабельний, але не порушує потік рядка

Inline-код має виглядати як код, але не кричати. Типові невдачі:

  • Inline-код має занадто багато паддінгу і виглядає як кнопка.
  • Inline-код не переноситься й спричиняє переповнення на мобільних через довгі токени.
  • Inline-код використовує крихітний шрифт, що розпливається при масштабуванні 125%.

Використовуйте тонкий фон, тонку рамку, помірний паддінг і white-space: break-spaces, щоб довгі токени могли переноситися при потребі.

Кодові блоки: переповнення — це ознака

Не переносіть кодові блоки за замовчуванням. Обернений код ламає копіювання, ховає відступи і створює неоднозначні команди. Правильне рішення — горизонтальна прокрутка всередині блоку (overflow: auto на pre).

Так, горизонтальна прокрутка не «гарна». Але це спосіб роботи терміналів. Користувачі до цього звикли.

Висота рядка і розмір табуляції

Блоки коду потребують трохи щільнішої висоти рядка, але не надто тісної. Також встановіть tab-size у розумне значення (2 або 4). За замовчуванням табуляція може відрізнятися і спотворювати відступи в прикладах.

Підсвітка синтаксису не врятує низький контраст

Бібліотеки підсвітки додають кольори, а не читабельність. Почніть з висококонтрастної пари переднього/фонового кольору, а потім накладайте кольори підсвітки. У темному режимі уникайте чисто чорного фону — він посилює ореоли й ускладнює виділення.

Цитати: виклики без слайду в мотиваційний плакат

Цитати в Markdown часто використовують як «нотатки/попередження». Це нормально, але стилі повинні робити їх читабельними й відокремленими від основного тексту, не загризаючи сторінку.

Використовуйте:

  • Ліву рамку (чіткий індикатор з малою візуальною вагою).
  • Слабкий фон (необов’язково, але корисно).
  • Зручні відступи, щоб багатопараграфні цитати не виглядали зжатими.

Уникайте надвеликих лапок, гігантської курсивності або драматичних змін шрифтів. Це не весільне запрошення.

Доступність і читабельність: контраст, фокус, виділення та рух

Документація для всіх, включно з тим, хто читає ваш runbook о 3 ранку з одним напіввідкритим оком і яскравістю ноутбука на «шкодування». Доступність — це не благодійність; це операційна коректність.

Контраст і вибір кольорів

Посилання повинні виглядати як посилання. Використовуйте підкреслення за замовчуванням або хоча б на hover із сильним контрастом. Не покладайтеся лише на колір для передачі значень усередині блоків коду: кольори підсвітки повинні доповнювати, а не замінювати базовий контраст.

Колір виділення (selection)

Люди часто копіюють з документації. Налаштований ::selection, що має достатній контраст, робить копіювання менш помилковим, особливо в коді. Тримайте його тонким, але помітним.

Контури фокусу

Якщо в додатку глобально прибрали контури фокусу, посилання в Markdown стають ворожими для клавіатурної навігації. Виправте це глобально або відновіть контури всередині .md. Підтримка клавіатурної навігації або є, або її немає.

Збільшення і розміри шрифтів

Не встановлюйте розмір шрифту контейнера Markdown у px і потім усе інше теж у px. Використовуйте rem і дозволяйте користувачам масштабувати. Мета дизайну — «читабельно», а не «ідентично».

Продуктивність і стабільність: запобігання зміщенням макета і конфліктам CSS

Більшість проблем із CSS проявляються як «виглядає дивно». Дорожчі — як «повільно» і «стрибає». Тут допомагають інстинкти SRE: шукайте системні причини, а не лише локальну гидоту.

Запобігання зсувам макета (CLS) на сторінках з Markdown

Поширені джерела зсувів макета в Markdown-контенті:

  • Пізнє завантаження шрифтів. Текст переформатується при заміні шрифту.
  • Зображення без розмірів. Сторінка стискається, а потім розширюється при завантаженні зображення.
  • Таблиці та блоки коду, що розширюються після рендеру. Якщо ваш CSS завантажується пізно, початковий рендер може бути без стилів.

Практичні пом’якшення:

  • Віддавайте перевагу системним стек-шифтам для документації або preloading шрифтів, які ви використовуєте.
  • Переконайтеся, що ваш Markdown-пайплайн додає width і height для зображень, коли це можливо, або використовуйте CSS-патерни з aspect-ratio, якщо контролюєте вихід.
  • Вбудовуйте мінімальний критичний CSS для .md (відступи, блоки коду, таблиці), щоб первинний рендер був стабільним.

Конфлікти CSS: чому scope — це необхідність

Вихід Markdown — це HTML: заголовки, списки, таблиці, pre, code, blockquote. Ваш додаток майже напевно вже стилізує ці елементи глобально. Якщо ви не scope-те стилі Markdown, ви або:

  • Пошкодите UI-компоненти «стилями для документа», або
  • Зіпсуєте документацію, успадкувавши стилі компонентів, призначені не для вмісту.

Scope-те через клас-контейнер. Якщо хочете додаткової безпеки, використовуйте cascade layers і тримайте Markdown в нижчому пріоритеті, ніж дизайн-система — або навпаки, залежно від правил продукту. Але виберіть підхід і дотримуйтеся його.

Швидкий план діагностики

Коли повідомляють «сторінка Markdown зламана», потрібно швидко знайти вузьке місце: рендеринг, конфлікти CSS, переповнення чи продуктивність. Ось порядок, що заощадить час.

Перше: ізолюйте область і конфлікти

  • Перевірте елемент-контейнер Markdown. Переконайтеся, що він має виділений клас-схему (наприклад, .md).
  • У DevTools перевірте обчислені стилі для h2, ul, pre, table. Якщо стилі походять з глобального ресету або бібліотеки компонентів — у вас конфлікт.
  • Тимчасово відключіть глобальні типографічні/ресет-стилі, щоб побачити, чи стає Markdown нормальним. Якщо так — виправляйте scope і порядок каскаду.

Друге: ідентифікуйте елемент, що викликає переповнення

  • Шукайте горизонтальну прокрутку на сторінці. Знайдіть елемент, що перевищує viewport.
  • Типові винуватці: table, pre, довгі нерозривні рядки в абзацах, зображення з фіксованою шириною.
  • Додайте containment: обгорніть таблиці, встановіть overflow: auto для блоків коду та застосуйте правила переносу слів тільки для прози — не для коду.

Третє: перевірте продуктивність і зсуви макета

  • Виміряйте CLS і поведінку заміни шрифтів. Якщо сторінка стрибає — CSS або шрифти приходять пізно.
  • Перевірте, чи мають зображення розміри. Якщо ні — виправте на етапі обробки Markdown.
  • Переконайтеся, що підсвітка синтаксису не виконує важкої роботи в DOM при кожному рендері.

Практичні завдання: команди, виводи та рішення

Ось завдання, які я реально запускаю, коли проблема зі стилями документації потрапляє в продакшн. Вони зосереджені на пайплайні: артефактах збірки, розмірі CSS, кешуванні та регресіях. Кожне містить команду, приклад виводу і рішення.

Завдання 1: Перевірити HTML-вихід Markdown

cr0x@server:~$ node -e "const fs=require('fs');const md=fs.readFileSync('sample.md','utf8');const {marked}=require('marked');console.log(marked.parse(md).slice(0,800));"
<h1>Runbook: Database failover</h1>
<p>If replication is behind, stop and verify.</p>
<h2>Prereqs</h2>
<ul>
<li>Access to <code>prod-bastion</code></li>
<li>Current primary is <code>db-01</code></li>
</ul>
<pre><code class="language-bash">...</code></pre>

Що це означає: Ви підтверджуєте, чи рендерер видає <pre><code> з класами, як саме вкладені списки формуються і чи є таблиці.

Рішення: Якщо вихід відрізняється між середовищами (сервер проти клієнта) — зупиніться. Узгодьте рендерер або нормалізуйте вихід ще до роботи з CSS.

Завдання 2: Перевірити, які CSS-правила перемагають (аудит колізій)

cr0x@server:~$ rg -n "pre\\s*\\{|code\\s*\\{|table\\s*\\{|blockquote\\s*\\{|h2\\s*\\{" dist/assets/*.css | head
dist/assets/app.4f19c8.css:231:pre{white-space:pre-wrap;word-break:break-word}
dist/assets/app.4f19c8.css:877:table{width:100%;font-size:12px}
dist/assets/docs.8a02b1.css:44:.md pre{overflow:auto;background:#0b1220}
dist/assets/docs.8a02b1.css:71:.md table{border-collapse:collapse}

Що це означає: Ви бачите глобальні правила типу pre{white-space:pre-wrap}, які можуть ламати блоки коду.

Рішення: Перенесіть стилі Markdown у scoped-файл, що завантажується після ресету, або змініть ресет, щоб він не впливав глобально на pre.

Завдання 3: Заміряти розмір CSS-пакета (стилі docs повинні бути легкими)

cr0x@server:~$ ls -lh dist/assets/*css | sed -n '1,10p'
-rw-r--r-- 1 cr0x cr0x 412K Dec 29 11:20 dist/assets/app.4f19c8.css
-rw-r--r-- 1 cr0x cr0x  18K Dec 29 11:20 dist/assets/docs.8a02b1.css

Що це означає: CSS для документації відокремлений і невеликий. Добре. Якщо він гігантський — ви, ймовірно, імпортуєте всю дизайн-систему для сторінки runbook.

Рішення: Якщо CSS для docs «пухкий», розбийте бандли або вбудуйте лише критичні стилі документації.

Завдання 4: Підтвердити, що gzip/brotli активні для CSS

cr0x@server:~$ curl -sI -H 'Accept-Encoding: br' http://localhost:8080/assets/docs.8a02b1.css | sed -n '1,12p'
HTTP/1.1 200 OK
Content-Type: text/css; charset=utf-8
Content-Encoding: br
Cache-Control: public, max-age=31536000, immutable
Vary: Accept-Encoding
ETag: "8a02b1"

Що це означає: Стиснення увімкнено, кешування immutable — це базовий рівень для швидкої доставки документації.

Рішення: Якщо стиснення відсутнє — виправте налаштування сервера, перш ніж оптимізувати селектори.

Завдання 5: Переконатися в кешуванні сторінок з відрендереним Markdown

cr0x@server:~$ curl -sI http://localhost:8080/docs/runbook/db-failover | sed -n '1,14p'
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Cache-Control: no-store
Vary: Accept-Encoding

Що це означає: Сторінка не кешується. Це може бути правильно для аутентифікованих runbook-ів, або помилково.

Рішення: Якщо контент статичний і публічний — ви хочете кешування. Якщо приватний або на користувача — залиште no-store, але переконайтеся, що ресурси кешуються агресивно.

Завдання 6: Знайти елемент, що викликає горизонтальний скрол

cr0x@server:~$ node -e "console.log('Use DevTools: run in console: [...document.querySelectorAll(\"body *\")].filter(e=>e.scrollWidth>e.clientWidth).slice(0,5).map(e=>[e.tagName,e.className,e.scrollWidth-e.clientWidth]) )')"
Use DevTools: run in console: [...document.querySelectorAll("body *")].filter(e=>e.scrollWidth>e.clientWidth).slice(0,5).map(e=>[e.tagName,e.className,e.scrollWidth-e.clientWidth])

Що це означає: Ви використовуєте надійний метод для пошуку елементів, що виходять за межі, замість гадок.

Рішення: Якщо винуватцями є TABLE або PRE — додайте обгортки/overflow. Якщо параграфи — застосуйте правила переносу для прози.

Завдання 7: Виявити нерозривні рядки в Markdown, що викликають overflow

cr0x@server:~$ perl -ne 'while(/(\S{80,})/g){print "$ARGV:$.:$1\n"}' docs/**/*.md | head
docs/runbook/net-debug.md:42:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
docs/howto/api.md:118:sha256:8c3d0f0a1c7b9d2e4f6a9b1c3d5e7f9a0b2c4d6e8f0a2b4c6d8e0f2a4c6d8e0f2

Що це означає: Ви знайшли токени, що зіпсують макет, якщо не дозволити перенесення в прозі.

Рішення: Додайте overflow-wrap: anywhere для .md p, якщо потрібно, але не застосовуйте його до pre/code.

Завдання 8: Підтвердити, що блоки коду не переносяться ресетом

cr0x@server:~$ rg -n "pre\\s*\\{[^}]*white-space:\\s*pre-wrap" dist/assets/app.*.css
dist/assets/app.4f19c8.css:231:pre{white-space:pre-wrap;word-break:break-word}

Що це означає: Ресет примушує переносити і ламає код, чутливий до відступів.

Рішення: Переоприділіть всередині .md як .md pre{white-space:pre;} і, якщо можливо, видаліть глобальне правило.

Завдання 9: Перевірити, що заголовки Markdown мають стабільні ID для анкерів

cr0x@server:~$ node -e "const s=require('fs').readFileSync('sample.html','utf8');const ids=[...s.matchAll(/id=\"([^\"]+)\"/g)].map(m=>m[1]);console.log(ids.filter(x=>x.includes(' ')).slice(0,10));"
[]

Що це означає: Немає ID із пробілами. Хороше. Нестабільні ID з небезпечними символами можуть ламати TOC і глибокі посилання.

Рішення: Якщо ID нестабільні — виправляйте генерацію slug-ів у рендерері, а не латаєте CSS.

Завдання 10: Переконатися, що таблиці обгорнуті (якщо рендерер підтримує пост‑обробку)

cr0x@server:~$ node -e "const {JSDOM}=require('jsdom');const fs=require('fs');const html=fs.readFileSync('sample.html','utf8');const dom=new JSDOM(html);const d=dom.window.document;d.querySelectorAll('.md table').forEach(t=>{const w=d.createElement('div');w.className='table-wrap';t.parentNode.insertBefore(w,t);w.appendChild(t);});console.log(d.querySelectorAll('.table-wrap table').length);"
3

Що це означає: Ви можете нав’язати обгортку навіть якщо автори забули її додати.

Рішення: Якщо таблиці регулярно переповнюються, автоматично додавайте обгортку в пайплайні. Не покладайтеся на дисципліну авторів.

Завдання 11: Перевірити, чи підсвітка синтаксису «надуває» DOM

cr0x@server:~$ node -e "const fs=require('fs');const html=fs.readFileSync('sample.html','utf8');const spans=(html.match(/

Що це означає: Підсвітка додала багато span-ів. Це впливає на продуктивність, особливо на слабких пристроях.

Рішення: Якщо число span-ів велике — розгляньте підсвітку на сервері для статичної документації, обмежте мови підсвітки або використайте легші теми.

Завдання 12: Інспектувати сигнали типу Lighthouse для зсувів в CI (швидко і брудно)

cr0x@server:~$ node -e "console.log('Run a headless audit in CI (example): npx playwright test --project=chromium; capture CLS via PerformanceObserver and fail if above threshold')"
Run a headless audit in CI (example): npx playwright test --project=chromium; capture CLS via PerformanceObserver and fail if above threshold

Що це означає: Ви перетворюєте «хтось помітив, що стрибає» на регресійний тест.

Рішення: Якщо CLS регресує — поводьтеся з цим як з боргом продуктивності: фіксуйте шрифти, розміри зображень або порядок завантаження CSS.

Завдання 13: Підтвердити, що шрифти не блокують рендеринг

cr0x@server:~$ rg -n "@font-face" dist/assets/*.css | head
dist/assets/app.4f19c8.css:12:@font-face{font-family:Inter;src:url(/assets/Inter.woff2) format("woff2");font-display:swap}

Що це означає: Налаштовано font-display: swap, що зменшує FOIT (flash of invisible text).

Рішення: Якщо swap відсутній і ви бачите невидимий текст — додайте його. Або використайте системний стек шрифтів для сторінок документації.

Завдання 14: Перевірити, що scope для CSS застосовано (немає глобальних селекторів тегів у стилях docs)

cr0x@server:~$ awk 'length($0)<500{print}' dist/assets/docs.8a02b1.css | rg -n "^(h1|h2|h3|p|pre|code|table|ul|ol|blockquote)\b" | head

Що це означає: Ні збігів: стилі docs не містять сирих селекторів тегів на верхньому рівні.

Рішення: Якщо бачите глобальні селектори — рефакторьте їх до .md h2 тощо. Не домовляйтеся з майбутніми відмовами.

Поширені помилки: симптом → корінь → виправлення

1) Симптом: блоки коду переносяться і команди стають некопійованими

Корінь: Глобальний ресет встановлює pre { white-space: pre-wrap; word-break: break-word; } або застосовує word-break до всього code.

Виправлення: В межах Markdown-області явно встановіть .md pre { overflow:auto; } і .md pre code { white-space: pre; }. Видаліть глобальні правила, якщо можете.

2) Симптом: на мобільному сторінці з’являється горизонтальний скрол, хоча таблиць немає

Корінь: Нерозривні токени в прозі (хеші, довгі URL, base64) у поєднанні з white-space: nowrap, успадкованим від стилю компонентів.

Виправлення: Встановіть .md p { overflow-wrap: anywhere; } або word-break: break-word; лише для прози. Залиште блоки коду як white-space: pre.

3) Симптом: списки виглядають як щільний абзац

Корінь: Ресет видалив відступи/паддінги списків; не відновлено ритм.

Виправлення: Відновіть паддінг і відступи списків всередині .md. Додайте невеликий вертикальний відступ для li і контролюйте відстані вкладених списків.

4) Симптом: таблиці руйнують макет або стискаються до нечитаємого тексту

Корінь: Ширина таблиці заштовхана в 100% плюс глобальний малий розмір шрифту, або таблиці не обгорнуті для overflow.

Виправлення: Обгорніть таблиці в overflow-x:auto. Тримайте розмір шрифту на базовому рівні. Якщо потрібно, дозволяйте min-width для колонок або покладайтеся на горизонтальну прокрутку.

5) Симптом: заголовки перекриваються липким хедером при переході за анкером

Корінь: Не налаштовано відступ при прокручуванні.

Виправлення: Додайте scroll-margin-top до заголовків всередині Markdown-області. Використовуйте значення, що відповідає висоті хедера.

6) Симптом: inline-код виглядає як клікабельні кнопки і відволікає

Корінь: Перестилізований code з великим паддінгом, сильним фоном і високою контрастністю.

Виправлення: Зменшіть паддінг, використайте тонкий фон і легку рамку, і тримайте розмір шрифту близьким до базового.

7) Симптом: темна тема документації нечитаема (надто тьмяна або надто яскрава)

Корінь: Кольори для світлого режиму інвертовані бездумно; блоки коду залишились світлими або використовують чистий чорний.

Виправлення: Визначте окремі змінні для темного режиму. Використовуйте темно‑морський фон, а не чистий чорний, і тримайте кольори посилань читабельними.

8) Симптом: «чому ця сторінка з документацією змінила стилі нашої сторінки оформлення?»

Корінь: Стилі Markdown використовують глобальні селектори тегів (h2 { ... }, table { ... }), які підключаються на всіх сторінках.

Виправлення: Scope-те все під класом-контейнером; завантажуйте стилі docs тільки де потрібно; розгляньте cascade layers.

Три корпоративні історії з маркдаунових фронтів

Інцидент через хибне припущення: «наш рендерер Markdown завжди видає однаковий HTML»

Компанія, з якою я працював, мала два шляхи рендерингу Markdown: server-side для публічної документації і client-side для внутрішніх runbook-ів. Це почалося як оптимізація: внутрішні сторінки рендерилися швидше після навігації, а команда порталу могла ітерувати без деплою бекенду.

Усі вважали «Markdown — це Markdown», тобто HTML буде однаковим. Насправді — ні. Серверний рендерер давав <pre><code class="language-bash">. Клієнтський — <pre class="language-bash"><code>. CSS, звісно, таргетив лише одну з форм.

Провал проявився під час інциденту: інженер on-call вставив команду з runbook‑а, але блок коду був перенесений посеред прапорця через глобальне правило для pre. Скопійована команда не спрацювала. Інженер намагався правити вручну, перестав довіряти runbook і почав імпровізувати. Час пішов боком.

Постмортем показав: корінь не в «поганому CSS», а в «двох рендерерах і єдиній ментальній моделі». Виправлення було двоплановим: нормалізувати вихід Markdown (вибрати один рендерер або пост‑обробляти до канонічної форми) і scope-нути CSS для блоків коду так, щоб він покривав обидві форми. Після цього додали тест, що рендерить той самий Markdown через обидва шляхи і дифить ключові вузли DOM.

Оптимізація, що відбилася боком: «давайте зменшимо DOM, прибравши wrapper-и»

Інша команда хотіла прискорити сторінки docs, спростивши згенерований HTML. Вони прибрали обгортки навколо таблиць і блоків коду, бо «зайві div-и — погано». Філософськи чисто. Операційно — хаос.

Без обгортки для таблиці єдиний спосіб запобігти її розриванню макета — застосувати overflow безпосередньо до таблиці або батьківського контейнера. Але overflow не працює з таблицями так, як люди бажають, а батьківський контейнер також містив звичайний текст. Раптом уся сторінка отримала горизонтальну прокрутку через одну широку таблицю.

Далі вони «оптимізували» ще: додали word-break: break-all до контейнера Markdown, щоб уникнути прокрутки. Це зупинило прокрутку, але зламало копіювання хешів, URL та inline-коду. On-call почали надходити тикети «документація псує команди». Ембаррасмент: контент був правильний; брехав CSS.

Зрештою вони повернули обгортки, але контрольовано: рендерер додає обгортки тільки коли це потрібно (є таблиця, є блок коду). Сторінка знову стала стабільною, а виграш у продуктивності від видалення wrapper-ів виявився майже уявним у порівнянні з витратами на перерахунки макета.

Сумна, але правильна практика, що врятувала день: «ми робимо снапшоти стилів документації і тестуємо дивні кейси»

Одна поважна організація ставилася до стилів документації як до API. Вони мали невеликий набір «злих Markdown»-фікстур: глибоко вкладені списки, довгі токени, широкі таблиці, змішаний inline-код, цитати з внутрішніми списками, зображення й блок коду з довгою командою.

Кожна зміна в типографічному CSS проходила візуальні снапшот-тести в CI на трьох ширинах viewport і в двох кольорових схемах. Це не було гламурно. Ніхто не отримав підвищення за «зменшив margin для li на 2px». Але це запобігло регресіям, що могли вдарити по найгіршій аудиторії: людям, які намагаються працювати.

Під час редизайну глобальний ресет зніс всі відступи списків і зробив pre переносним. Фікстури спіймали це до релізу. Виправлення — scoped override всередині .md і правило: глобальні ресети не можуть зачіпати сирі HTML-теги без погодження власника дизайн-системи.

Така нудна практика — фікстури, снапшоти, scoped CSS — дозволила редизайну вийти, не перетворивши документацію на побічну шкоду. Ось як виглядає «надійність», коли система майже повністю текстова.

Чеклісти / покроковий план

Покроковий план впровадження розумної CSS для Markdown

  1. Впровадьте клас-контейнер для Markdown. Обгорніть весь згенерований Markdown у <div class="md">...</div>. Без винятків.
  2. Визначте змінні для типографіки та відступів. Встановіть базовий стек шрифтів, колір тексту, колір посилань, межі та шкалу відступів.
  3. Відновіть типографічний ритм. Встановіть відступи для p, h2, h3 і списків всередині .md.
  4. Ускріпіть поведінку переповнення. Блоки коду: overflow:auto. Таблиці: обгортка зі скролом. Проза: дозвольте переносити довгі токени.
  5. Забезпечте роботу анкорів. Додайте scroll-margin-top до заголовків і переконайтеся, що ID стабільні.
  6. Обробіть темну тему свідомо. Використовуйте CSS-змінні з prefers-color-scheme: dark override-ами.
  7. Додайте стилі для друку. Особливо для блоків коду і посилань.
  8. Створіть фікстури Markdown. Включіть найгірші приклади. Тримайте набір маленьким, але «підлим».
  9. Автоматизуйте перевірки. Снапшот-тести і простий детектор overflow. Фейлити збірки при регресіях.
  10. Швидко підключайте scoped CSS тільки там, де потрібно. Якщо docs на окремому маршруті, не підключайте стилі Markdown скрізь.

Перевірка перед запуском зміни стилів Markdown

  • Зміна зачіпає тільки область .md?
  • Тестували широку таблицю на вузькому вікні?
  • Тестували довгий нерозривний токен у абзаці?
  • Залишився довгий рядок коду копійованим?
  • Анкорні посилання приводять під липким заголовком правильно?
  • Темна тема зберігає контраст для посилань і коду?
  • Печать дає читабельні блоки коду?

Чекліст відкату

  • Чи можете ви відключити нові стилі docs через feature-flag або конфігурацію маршруту?
  • Чи є у вас попередній відомо‑добрий артефакт стилів, який можна швидко повернути?
  • Чи є мінімальний «safe mode» стиль (системні шрифти, базові відступи, правила overflow) для відкату?

FAQ

1) Чи слід використовувати CSS-reset для контенту Markdown?

Не напряму. Використовуйте ресет на рівні додатку, якщо потрібно, але потім явно відновіть відступи і типографіку всередині .md. Markdown потребує відступів; нульові відступи — ворожі для читача.

2) Чи нормально переносити блоки коду замість прокрутки?

Тільки якщо ви готові миритися з порушеним копіюванням і неоднозначними відступами. Для runbook-ів і команд краще горизонтальний скрол. Якщо вам потрібно переносити «code-like» прозовий вміст — робіть це опційно.

3) Як зробити таблиці адаптивними без горизонтального скролу?

Ви можете перетворювати рядки в картки за допомогою CSS, але це припускає контроль над схемою. Для довільних Markdown-таблиць горизонтальна прокрутка — найнадійніша поведінка.

4) Чому моя Markdown-сторінка виглядає інакше між середовищами?

Різні рендерери (або різні версії) видають різну HTML-структуру. Спочатку вирішіть проблему з рендерером. CSS може тимчасово прикрити різницю, але ви будете наздоганяти крайові випадки нескінченно.

5) Чи потрібні окремі стилі для server- і client-rendered Markdown?

Потрібні стилі, що відповідають виданому HTML. Кращий підхід — зробити HTML однаковим у всіх шляхах рендерингу, а потім написати один scoped-стайлшит.

6) Як запобігти впливу стилів Markdown на решту додатку?

Scope-те через клас-контейнер і уникайте глобальних селекторів тегів. Також розгляньте завантаження CSS для docs тільки на відповідних маршрутах. «Ми будемо обережні» — не стратегія.

7) Що з темами підсвітки синтаксису — обрати одну і забути?

Обрати тему з хорошим контрастом і без дрібних шрифтів. Потім перевірити видимість виділення і копіювання. Красива підсвітка, що стає нечитаемою в темній темі, — регресія.

8) Чи має Markdown-контент використовувати ту ж типографіку, що і UI продукту?

Зазвичай так для основного тексту, але блоки коду завжди повинні використовувати надійний моноширинний стек. Тримайте документацію знайомою, але не нав’язуйте UI-обмеження, що шкодять читабельності (наприклад, вузькі картки або дрібні шрифти).

9) Можна просто використати популярну бібліотеку «Markdown CSS»?

Можна, але ставтеся до неї як до стороннього коду: аудит, scope і тестування найгірших випадків. Багато бібліотек орієнтовані на блог-пости, а не на runbook-и.

10) Який мінімум CSS потрібен для прийнятного рендерингу Markdown?

Scoped-відступи для заголовків/параграфів/списків, стилі переповнення для блоків коду, обгортка для таблиць і стилі посилань. Решта — питання якості життя.

Висновок: кроки на цей тиждень

Стилізація Markdown ускладнюється, коли ви вважаєте її декоративною. Ставтеся до неї як до продакшн-інтерфейсу: напівдовірений вхід, змінний вихід і реальні користувачі, які від нього залежать.

Зробіть наступне:

  1. Обгорніть згенерований Markdown у виділений клас scope і припиніть стилізувати сирі теги глобально.
  2. Впровадьте базу: послідовні відступи для заголовків/списків, overflow для блоків коду і обгортки для таблиць.
  3. Зберіть невеликий набір «потворного» Markdown і робіть снапшоти на кількох ширинах і кольорових схемах.
  4. Додайте простий детектор overflow і контроль CLS у CI, щоб «на моєму машині виглядає нормально» перестало бути основним QA.

Коли ваша документація стане стабільною, ваш on-call працюватиме швидше, онбординг буде плавнішим, а UI-команда перестане вважати Markdown як самостійну дику підсистему. Це реальний виграш у надійності — просто зі складу текстів.

← Попередня
Чому синтетичні бенчмарки брешуть (і як їх викрити)
Наступна →
Вентилятори встановлені навпаки: коли потік повітря йде не туди

Залишити коментар