Стилизация компонентов UI

Компоненты Jmix UI предоставляют дополнительную функциональность (например, надпись, текст помощи, сообщение об ошибке, всплывающую подсказку и т.д.), которые делают их более сложными, чем нативные HTML-элементы. Они также используют возможность HTML под названием shadow DOM, которая инкапсулирует их внутреннюю HTML-структуру и изолирует ее от глобального CSS страницы.

element shadow dom
Figure 1. Структура HTML-элемента компонента

В связи с этим UI-компоненты не могут быть стилизованы с использованием нативных селекторов элементов HTML, таких как input {…} и button {…}. Вместо этого каждый компонент предоставляет набор частей (parts) и состояний (states), которые могут использоваться в качестве селекторов в блоках стилей CSS.

Например, если вы хотите применить цвет фона и границу к textField, вам нужно применить стили к теневой части (shadow part) под названием input-field:

vaadin-text-field::part(input-field) {
  background: white;
  border: 1px solid black;
}
styled textfield
Figure 2. TextField с фоном и границей

Стилизуемые части компонентов

Существует несколько основных типов частей, каждая из которых связана с разным типом селектора CSS.

Корневые элементы

Каждый компонент пользовательского интерфейса имеет корневой HTML-элемент, название которого начинается с префикса vaadin- или jmix-, например vaadin-button или jmix-value-picker. Например, цвет фона кнопки можно изменить следующим образом:

vaadin-button {
  background: gray;
}

Теневые части

Теневые части - это элементы внутри shadow DOM компонента, стилизуемые с помощью селектора ::part(). Например:

vaadin-text-field::part(input-field) {
    background: white;
    border: 1px dotted black;
}

Обычные дочерние элементы

Обычные (Light DOM) дочерние HTML-элементы стилизуются с помощью селектора >. Например, элемент label компонента TextField можно стилизовать следующим образом:

vaadin-text-field > label {
    font-weight: bold;
}

Состояния компонентов

Большинство состояний корневых элементов компонентов и их различных частей предоставляются через атрибуты состояния и стилизуются с использованием селекторов атрибутов вида component-name[state]. Например, отключенные кнопки идентифицируются селектором [disabled]:

vaadin-button[disabled] {
  background: lightgray;
  color: darkgray;
}

Варианты стилей компонентов

Многие UI-компоненты поставляются со встроенными вариантами стилей, которые можно использовать для изменения цвета, размера или других визуальных аспектов отдельных экземпляров компонентов через Java API addThemeVariants()/addThemeNames() или XML-атрибут themeNames:

<hbox>
    <button text="Primary" themeNames="primary"/>
    <button text="Success" themeNames="success"/>
    <button text="Tertiary" themeNames="tertiary"/>
</hbox>
button theme variants
Figure 3. Кнопки с примененными вариантами темы

Эти варианты применяются к атрибуту theme корневых элементов компонентов, и могут быть адресованы с помощью селекторов атрибутов CSS:

vaadin-button[theme~="primary"] {
    background-color: purple;
}

Стилизация экземпляров компонентов

Если вам нужно применить стили к конкретному экземпляру компонента, вы можете использовать атрибут classNames, например:

<textField classNames="bordered"/>
vaadin-text-field.bordered::part(input-field) {
    background: white;
    border: 1px solid black;
}

Динамическая генерация стилей

Если вам нужно динамически генерировать стили на основе какой-либо пользовательской логики, вы можете использовать API Style.

Один подход заключается в установке встроенных свойств CSS на корневой элемент компонента, например:

@ViewComponent
private JmixButton myBtn;

@Subscribe
public void onInit(final InitEvent event) {
    myBtn.getStyle().set("color", "white");
    myBtn.getStyle().set("background-color", "purple");
}

Недостатком этого подхода является то, что невозможно применить стили к частям компонентов или на основе их состояний.

Другой вариант - использовать свойства CSS, будь то встроенные свойства Lumo или пользовательские, которые статически применяются с помощью CSS, но значения которых устанавливаются через логику приложения:

html {
    --my-button-text-color: darkblue;
    --my-button-bg-color: yellow;
}

vaadin-button.my-button {
  color: var(--my-button-text-color);
  background-color: var(--my-button-bg-color);
}
<button id="myBtn" text="Button" classNames="my-button"/>
@Subscribe
public void onInit(final InitEvent event) {
    UI.getCurrent().getElement().getStyle().set("--my-button-text-color", "white");
    UI.getCurrent().getElement().getStyle().set("--my-button-bg-color", "purple");
}

Преимуществом этого подхода является то, что вы можете адресовать части компонентов и несколько компонентов с помощью одного и того же свойства стиля. Это можно использовать, например, для того, чтобы позволить пользователям настраивать стили интерфейса, сохранять их в базе данных и загружать при входе в систему.