Пара тегов <fieldset> и <legend> чаще всего вспоминается в контексте форм: ими принято группировать связанные поля и подписывать группу — например, «Адрес доставки» над набором из четырёх инпутов. Подробнее про эту прямую роль — в посте про советы по разметке форм. Но у этой пары есть особенность, ради которой её иногда тащат и за пределы форм: <legend> — единственный элемент в HTML, который браузер рендерит так, как будто он «разрезает» верхнюю границу <fieldset> и встаёт прямо в линию рамки.
Никаких position: absolute, никаких ::before с белым фоном поверх линии — это нативное поведение элемента. Из этого факта вырастает целое семейство декоративных эффектов: заголовки с линиями по бокам, подписанные рамки, многоугольные обводки с текстом. Разберём, как этот механизм устроен и где он реально полезен (а где — ломает семантику).
Почему <legend> ведёт себя не как обычный inline-элемент
Если открыть DevTools и посмотреть на дефолтные стили <fieldset> в Chrome, видно характерное:
fieldset {
display: block;
margin-inline: 2px;
padding-block: 0.35em 0.625em;
padding-inline: 0.75em;
border: 2px groove rgb(192, 192, 192);
min-inline-size: min-content;
}
А у <legend> — ничего особенного. Магия не в стилях, а в layout-движке: когда фрейм fieldset считает высоту своей верхней границы, он резервирует место под legend и «вырезает» ровно ту полоску пикселей, которую legend занимает по ширине. Никакой другой HTML-элемент так не умеет.
Подпись (<legend>) внутри рамки можно двигать и настраивать тремя простыми способами:
- изменить margin на самом <legend> — это как двигать текст вправо или влево вдоль верхней линии. Хочешь по центру? Пиши margin-inline: auto. Хочешь чуть отступить от левого края? Пиши margin-inline-start: 30px (или сколько нужно).
- поменять padding для <legend> — это создаёт пустое пространство вокруг текста. Чем больше padding, тем больше разрыв в рамке.
- добавить border и background на самом <fieldset> — это самый главный «секрет». Именно здесь решается, будет ли рамка сверху, как она выглядит и где именно должна быть «дырка» под подпись. Большинство красивых решений крутятся именно вокруг этого.
Этого достаточно, чтобы собрать четыре нетривиальных эффекта.
Рецепт 1: заголовок с линиями по бокам
Один из самых частых паттернов в дизайне: разделитель в виде надписи «РАЗДЕЛ» с уходящими в стороны линиями. Обычно его собирают через ::before/::after с background-color или через flex с двумя пустыми div. На <fieldset> то же самое получается из четырёх строчек CSS:
<fieldset class="divider">
<legend>Раздел</legend>
</fieldset>
.divider {
inline-size: 320px;
block-size: 0;
border: 1px solid transparent;
border-block-start-color: #4a5568;
padding: 0;
}
.divider legend {
margin-inline: auto;
padding-inline: 12px;
color: #4a5568;
font-size: 14px;
text-transform: uppercase;
letter-spacing: 2px;
}
Идея в трёх трюках. Во-первых, у рамки прозрачный border по всему периметру, и только border-block-start-color — видимый. Из четырёх сторон рамки остаётся одна. Во-вторых, block-size: 0 схлопывает высоту самого fieldset — линия становится одиночной горизонталью. В-третьих, margin-inline: auto центрирует подпись по этой линии, а её собственный padding-inline создаёт нужный разрыв в обводке.
Никаких флекс-обёрток, никаких пустых div-ов — одна семантическая пара тегов, и тот же визуал, что обычно собирают тремя элементами.
Рецепт 2: подпись на смещённой стороне рамки
Если рамка нужна целиком, а подпись хочется сдвинуть в произвольное место верхней линии — работает тот же margin-inline-start:
.card {
inline-size: 280px;
padding: 24px;
border: 2px solid #3182ce;
border-radius: 8px;
}
.card legend {
margin-inline-start: 16px;
padding-inline: 8px;
color: #3182ce;
font-weight: 600;
}
Поведение margin-inline-start здесь логическое: на LTR-страницах подпись прижимается к левому углу, на RTL — к правому. Старое margin-left сработает не хуже, но потеряет автоматическую адаптацию под направление текста (подробности про логические свойства — в мануале по логическим свойствам).
Рецепт 3: рамка-полигон с подписью на каждой стороне
Здесь начинается то, ради чего эту пару тегов в принципе вспоминают за пределами форм. Если нужна квадратная обводка, у которой на каждой из четырёх сторон своя подпись, обычно собирают четыре линии через четыре отдельных ::before/::after на двух разных элементах — и упираются в потолок (псевдоэлементов всего два на элемент). С fieldset эта задача решается прямолинейно: четыре <fieldset>, каждый рисует только верхнюю линию своей рамки, и три из них повёрнуты на 90deg, 180deg и -90deg.
<div class="polygon">
<fieldset><legend>CSS</legend></fieldset>
<fieldset><legend>HTML</legend></fieldset>
<fieldset><legend>JavaScript</legend></fieldset>
<fieldset><legend>TypeScript</legend></fieldset>
</div>
.polygon {
position: relative;
inline-size: 280px;
block-size: 280px;
}
.polygon fieldset {
position: absolute;
inset: 0;
margin: 0;
padding: 0;
border: 8px solid transparent;
border-block-start-color: #2d3748;
}
.polygon legend {
margin-inline: auto;
padding-inline: 12px;
color: #2d3748;
font-weight: 600;
}
.polygon fieldset:nth-of-type(2) { transform: rotate(90deg); }
.polygon fieldset:nth-of-type(3) { transform: rotate(180deg); }
.polygon fieldset:nth-of-type(3) legend { transform: rotate(180deg); }
.polygon fieldset:nth-of-type(4) { transform: rotate(-90deg); }
Логика тут такая: все <fieldset> кладутся точно друг на друга (через inset: 0 — они полностью перекрываются). У каждого fieldset остаётся только верхняя граница, остальные стороны убирают. А потом с помощью transform: rotate() каждый такой fieldset поворачивают так, чтобы его верхняя граница оказалась на нужной стороне квадрата: один — сверху, один — справа, один — снизу, один — слева. Подпись (легенду) на нижней стороне приходится дополнительно повернуть на 180°, иначе текст будет вверх ногами.
Эффект, который сложно повторить псевдоэлементами, без оверхеда из восьми вспомогательных тегов.
Рецепт 4: бегущая подпись по верхней границе
Раз позицией legend управляет CSS-свойство margin, его можно анимировать — и подпись поедет вдоль рамки. Маленький, но запоминающийся эффект, например, для блока «Что нового»:
.ticker {
inline-size: 380px;
block-size: 80px;
border: 1px solid #cbd5e0;
}
.ticker legend {
padding-inline: 12px;
color: #2b6cb0;
font-weight: 600;
animation: slide 6s ease-in-out infinite alternate;
}
@keyframes slide {
from { margin-inline-start: 10px; }
to { margin-inline-start: 210px; }
}
Анимация работает в любом браузере с поддержкой @keyframes — то есть в любом живом. Стоит заранее учесть пользователей с prefers-reduced-motion и обнулять анимацию для них — правило хорошего тона для любых движущихся элементов интерфейса.
Когда такие приёмы ломают семантику
Прежде чем тащить <fieldset> в каждую вторую секцию страницы, стоит вспомнить, для чего этот тег был придуман.
Согласно спецификации HTML, <fieldset> — это группа элементов управления формой, а <legend> — подпись для этой группы. Скрин-ридеры обрабатывают пару особым образом: при фокусе на любом инпуте внутри группы озвучивается текст legend как часть имени поля. Это полезное поведение для радиокнопок и чекбоксов, где без контекста группы вопрос становится бессмысленным («Маленький»? Маленький что?).
Если использовать пару чисто декоративно — пустой <fieldset> без формы внутри — для скрин-ридера это превратится в «группа: Раздел, конец группы»: лишний шум, никак не связанный с контентом. Несколько правил, чтобы декоративное применение не ухудшало доступность:
- Если внутри fieldset действительно есть группа полей — всё хорошо, используем смело, это и есть прямое назначение тега.
- Если fieldset нужен только ради визуального разделителя из Рецепта 1 — лучше всё-таки взять <hr> с псевдоэлементами или div с flex-разметкой. <hr> семантически читается как тематический разрыв, что для разделителя точно.
- Если очень хочется fieldset вне формы (например, ради Рецепта 3 c полигональной рамкой и подписями) — накрыть его role="presentation" и убрать у legend семантику тоже. Тогда скрин-ридер прочитает только текст подписей, без «группа: …».
<fieldset role="presentation" class="polygon-side">
<legend>CSS</legend>
</fieldset>
Альтернативные подходы
Декоративное поведение <fieldset>/<legend> — не единственный способ получить текст внутри рамки. Стоит знать полный набор трюков, чтобы выбор был осознанным.
- Псевдоэлементы и flex. Для горизонтальной линии с заголовком посередине (Рецепт 1) минимальный вариант — display: flex на родителе и ::before/::after с flex: 1 и border-block-start. Семантически чище, элемент остаётся обычным <h2>, который и так у страницы есть.
- border-image с SVG-маской. Полигональную рамку с подписями (Рецепт 3) можно сделать через нарисованный SVG-фон и одну обычную <div>. Это требует один раз нарисовать SVG, но даёт полный контроль над стилизацией линий и позволяет рамкам быть произвольной формы, не только квадрату.
- CSS Grid. Для двух-трёхпозиционных подписей в рамке (например, угол и центр) можно собрать сетку из grid-template-areas, у которой только нужные ячейки имеют видимые границы. Так делают cheat-sheet-обложки в дизайн-системах.
Базовая мысль: пара fieldset/legend выигрывает там, где нужен один приём из четырёх рецептов выше с минимумом разметки. Как только в дизайне появляется хотя бы один нестандартный элемент — градиент в линии рамки, неровные углы, отзывчивая раскладка подписей — почти всегда выгоднее переезжать на flex с псевдоэлементами или на SVG.
Комментарии (0)