Сравнение
Здесь ECSS сравнивается с популярными подходами к стилизации компонентов на одном примере — кнопке с вариантами оформления. Главное отличие ECSS: логика «какой стиль применить» живёт в файле стилей, а не в компоненте, и при этом полностью типизирована.
CSS Modules
CSS Modules решают проблему глобальных имён, но логика состояний остаётся в компоненте. Каждый новый вариант — правки в двух местах: CSS-файл и JS-компонент.
/* Button.module.css */
.button {
display: inline-flex;
padding: 8px 16px;
border-radius: 6px;
cursor: pointer;
}
.primary {
background: #646cff;
color: #fff;
}
.danger {
background: #e53e3e;
color: #fff;
}
.ghost {
background: transparent;
border: 1px solid currentColor;
}
.disabled {
opacity: 0.4;
cursor: not-allowed;
}import styles from './Button.module.css';
function Button({ variant, disabled, children }) {
const className = [
styles.button,
variant === 'primary' && styles.primary,
variant === 'danger' && styles.danger,
variant === 'ghost' && styles.ghost,
disabled && styles.disabled,
]
.filter(Boolean)
.join(' ');
return <button className={className}>{children}</button>;
}С ECSS всё объявляется в одном файле, а компонент не знает ни о каких классах:
/* Button.ecss */
@enum Variant {
values: "primary", "danger", "ghost";
}
@block Button {
@param --variant Variant;
@param --disabled? boolean;
display: inline-flex;
padding: 8px 16px;
border-radius: 6px;
cursor: pointer;
@if (--variant == "primary") {
background: #646cff;
color: #fff;
}
@if (--variant == "danger") {
background: #e53e3e;
color: #fff;
}
@if (--variant == "ghost") {
background: transparent;
border: 1px solid currentColor;
}
@if (--disabled) {
opacity: 0.4;
cursor: not-allowed;
}
}import { EButton } from './Button.ecss';
<EButton as="button" params={{ variant: 'primary' }}>
Кнопка
</EButton>;- Не нужно вручную собирать
className - Логика состояний живёт рядом со стилями
- TypeScript знает допустимые значения
variant— передать несуществующее не получится
styled-components
styled-components позволяют писать стили прямо в JS, используя props для вариантов. Однако у подхода есть несколько существенных минусов: CSS генерируется в runtime при каждом рендере, сама библиотека добавляет в бандл десятки килобайт, а шаблонные строки со вставками JS быстро превращаются в трудночитаемую смесь двух языков.
import styled, { css } from 'styled-components';
const Button = styled.button<{
variant: 'primary' | 'danger' | 'ghost';
disabled?: boolean;
}>`
display: inline-flex;
padding: 8px 16px;
border-radius: 6px;
cursor: pointer;
${({ variant }) =>
variant === 'primary' &&
css`
background: #646cff;
color: #fff;
`}
${({ variant }) =>
variant === 'danger' &&
css`
background: #e53e3e;
color: #fff;
`}
${({ variant }) =>
variant === 'ghost' &&
css`
background: transparent;
border: 1px solid currentColor;
`}
${({ disabled }) =>
disabled &&
css`
opacity: 0.4;
cursor: not-allowed;
`}
`;ECSS обрабатывается на этапе сборки — в браузер попадает обычный статичный CSS:
/* Button.ecss */
@enum Variant {
values: "primary", "danger", "ghost";
}
@block Button {
@param --variant Variant;
@param --disabled? boolean;
display: inline-flex;
padding: 8px 16px;
border-radius: 6px;
cursor: pointer;
@if (--variant == "primary") {
background: #646cff;
color: #fff;
}
@if (--variant == "danger") {
background: #e53e3e;
color: #fff;
}
@if (--variant == "ghost") {
background: transparent;
border: 1px solid currentColor;
}
@if (--disabled) {
opacity: 0.4;
cursor: not-allowed;
}
}import { EButton } from './Button.ecss';
<EButton as="button" params={{ variant: 'primary' }}>
Кнопка
</EButton>;- Нет runtime-оверхеда: CSS генерируется один раз при сборке
- Работает с SSR без дополнительной настройки
- Стили не смешиваются с JS-логикой