В HTML есть элемент
<slot> и атрибут с таким же названием — slot. Работают они только с Shadow DOM. Цель элемента <slot> — обозначить стандартную или именованную ячейку для перемещения элементов из DOM в Shadow DOM.Цель атрибута
slot — указать имя ячейки, которую создаёт <slot name="…">, куда должен попасть элемент. Там ещё хитрые правила проникновения стилей в Shadow DOM и селекторы :slotted() и :has-slotted. Но это всё про Shadow DOM.Я тут подумал: а если использовать атрибут
slot без Shadow DOM? Так он не работает, но современный CSS на многое способен. Можно создать grid с именованными ячейками и распределить по ним элементы с помощью attr() и grid-area.input-wrapper {
display: inline-grid;
grid-template-areas:
'label label label'
'before input after'
'info info info'
;
> :not([slot]) {
display: none !important;
}
> [slot] {
--slot: attr(
slot
type(label | before | input | after | info),
invalid
);
display: if(
style(--slot: invalid): none !important;
else: revert;
);
grid-area: if(
style(--slot: invalid): auto;
else: var(--slot);
);
}
}<input-wrapper>
<label
for="pwd"
slot="label"
>
Пароль
</label>
<input
type="password"
id="pwd"
autocomplete="new-password"
slot="input"
>
<button
type="button"
commandfor="pwd"
command="--toggle-password"
slot="after"
aria-pressed="false"
>
<!-- ... -->
</button>
<p slot="info">
Пароль должен содержать...
</p>
</input-wrapper>
Сейчас объясню что тут происходит. Внутри
input-wrapper объявлена сетка из трёх колонок с именованными ячейками label, before, input, after и info. Прямые потомки без атрибута slot скрываются, потому что он нужен.Функция
attr() считывает значение из атрибута slot и приводит его к заданному типу. Функция type() определяет CSS-тип значения, который представлен в виде перечисления доступных имён ячеек (как Union Type в Typescript).Если в
slot указано неподдерживаемое значение, то его не получится привести к типу, указанному в type(). В таком случае используется значение по умолчанию invalid. Результат attr() записывается в пользовательское свойство --slot.Функция
if() в свойстве display проверяет значение свойства --slot. Если там invalid, то есть в атрибуте задано неподдерживаемое имя ячейки, то такой элемент скрывается. Иначе revert возвращает заданное ранее значение.В свойстве
grid-area функция if() так же проверяет свойство --slot. При invalid устанавливается значение auto, но это не важно, потому что элемент будет скрыт. А если там допустимое значение, то элемент попадает в нужную ячейку.Это работает в браузерах с поддержкой расширенной функции
attr() и if(). Элементы можно менять местами и они всё равно попадут в нужные ячейки сетки. Ячейки могут быть пустыми, в примере нет элемента для слота before.Это решение можно слегка деградировать, отказавшись от
if() и attr() . Кода будет больше, но браузерная поддержка расширится и применить можно будет уже сейчас. Допустимые значения атрибута и ячейки нужно задать вручную.input-wrapper {
/* ... */
> :is(
:not([slot]),
[slot]:not(
[slot="label"],
[slot="before"],
...
)
)
{
display: none !important;
}
> [slot="label"] {
grid-area: label;
}
> [slot="before"] {
grid-area: before;
}
...
}Я использую пользовательский элемент
<input-wrapper> как обёртку, без JS. Удобно и логично, что имя контейнера намекает на суть. Можно использовать <div class="input-wrapper">, если так удобнее и привычнее.Так на HTML и CSS можно создавать компоненты с некоторым API «слотов». И использование для этого существующего атрибута
slot выглядит логично, хотя и не по назначению. Можно в случае чего заменить на data-slot.#css