Кнопки – важнейший интерактивный компонент наших сайтов. Именно с их помощью пользователи совершают большинство действий, значит, их дизайн очень важен.

Базовые стили

Начинаем с определения основных стилей для всех кнопок:

:root {
    --btn-font-size: 1em;
    --btn-radius: 0.25em;
}

.btn {
    display: inline-flex;
    position: relative;
    white-space: nowrap;
    text-decoration: none;
    line-height: 1;

    padding: var(--space-xs) var(--space-sm);
    border-radius: var(--btn-radius);
    font-size: var(--btn-font-size);
    color: var(--color-link);
}

Эти свойства не будут изменяться для кнопок разных видов и размеров:

  • position: relative потребуется для позиционирования иконок внутри кнопок;
  • white-space: nowrap запрещает перенос текста подписи на другую строчку;
  • text-decoration: none убирает подчеркивание, так что класс .btn теперь можно устанавливать и для ссылок.

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

Вариации стилей

Чаще всего для кнопок создаются следующие состояния:

  • .btn--primary – базовый стиль активных кнопок;
  • .btn--secondary – дополнительная версия кнопок, предназначенных для менее важных действий, хорошо сочетающаяся с основной (primary) или акцентной (accent);
  • .btn--accent – используется для выделения кнопок с действиями, требующими повышенного внимания пользователя (например, удаление);
  • .btn[disabled] – указывает пользователю, что кнопка неактивна.
/* themes */
.btn--primary {
    // main button
    background-color: var(--btn-primary-bg);
    color: var(--btn-primary-label);
    @include fontSmooth;

    &:visited {
        color: var(--btn-primary-label);
    }

    &:hover {
        background-color: var(--btn-primary-hover);
    }

    &:active {
        background-color: var(--btn-primary-active);
    }
}

.btn--secondary {
    // subtle version, used for secondary actions or in combo with primary/accent button
    background-color: var(--btn-secondary-bg);
    color: var(--btn-secondary-label);

    &:visited {
        color: var(--btn-secondary-label);
    }

    &:active {
        background-color: var(--btn-secondary-active);
    }
}

.btn--accent {
    // used to draw special attention to the button (e.g. destructive actions)
    background-color: var(--btn-accent-bg);
    color: var(--btn-accent-label);
    @include fontSmooth;

    &:visited {
        color: var(--btn-accent-label);
    }

    &:hover {
        background-color: var(--btn-accent-hover);
    }

    &:active {
        background-color: var(--btn-accent-active);
    }
}

/* feedback */
.btn[disabled] {
    cursor: not-allowed;
    background-color: var(--btn-disabled-bg);
    color: var(--btn-disabled-label);
    box-shadow: none;
    opacity: 0.6;

    &:visited {
        color: var(--btn-disabled-label);
    }
}

Все цвета определяются в отдельном файле _colors.scss. Подробнее о работе с цветами и темами в дизайн-системе вы можете прочитать в третьей статье серии.
Добавим в этот файл цвета для кнопок:

/* --------------------------------

Colors

-------------------------------- */

:root {
    /* main colors */
    --color-primary: #286bf4;
    --color-primary-light: color-mod(var(--color-primary) tint(15%));
    --color-primary-dark: color-mod(var(--color-primary) shade(15%));
    --color-primary-bg: color-mod(var(--color-primary) alpha(20%));

    --color-accent: #f5414f;
    --color-accent-light: color-mod(var(--color-accent) tint(15%));
    --color-accent-dark: color-mod(var(--color-accent) shade(10%));
    --color-accent-bg: color-mod(var(--color-accent) alpha(20%));

    /* shades - generated using chroma.js - 12 steps */
    --black: #1d1d21;
    --gray-10: #2e2e31;
    --gray-6: #7b7a7d;
    --gray-4: #a5a5a6;
    --gray-3: #bbbbbc;
    --gray-2: #d1d0d2;
    --gray-1: #e8e7e8;
    --white: white;

    /* buttons */
    --btn-primary-bg: var(--color-primary);
    --btn-primary-hover: var(--color-primary-light);
    --btn-primary-active: var(--color-primary-dark);
    --btn-primary-label: var(--white);

    --btn-secondary-bg: var(--gray-1);
    --btn-secondary-active: var(--gray-2);
    --btn-secondary-label: var(--gray-10);

    --btn-accent-bg: var(--color-accent);
    --btn-accent-hover: var(--color-accent-light);
    --btn-accent-active: var(--color-accent-dark);
    --btn-accent-label: var(--white);

    --btn-disabled-bg: var(--gray-2);
    --btn-disabled-label: var(--gray-10);
}

Миксин fontSmooth предназначен для улучшения рендеринга шрифтов при использовании светлого текста на темном фоне. Этот стиль используется довольно часто для разных элементов, поэтому его стоит вынести в отдельный миксин.

// edit font rendering -> tip: use for light text on dark backgrounds
@mixin fontSmooth {
    -webkit-font-smoothing: antialiased;
	-moz-osx-font-smoothing: grayscale;
}

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

Размеры

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

Хороший подход: определить переменные размера с помощью calc().

:root {
    --btn-font-size: 1em;
    --btn-sm: calc(var(--btn-font-size) - 0.2em);
    --btn-md: calc(var(--btn-font-size) + 0.2em);
    --btn-lg: calc(var(--btn-font-size) + 0.4em);

    --btn-radius: var(--radius);
}

/* button size */
.btn--sm {
    font-size: var(--btn-sm);
}

.btn--md {
    font-size: var(--btn-md);
}

.btn--lg {
    font-size: var(--btn-lg);
}

.btn--full-width {
    display: flex;
    width: 100%;
    justify-content: center;
}

Кнопки с иконками

В предыдущей статье серии мы разобрались с выравниванием иконок и текста. Тот же самый класс .icon-text-aligner можно использовать для выравнивания подписей и иконок внутри кнопок.

<button class="btn btn--primary icon-text-aligner">
    <span>Button</span>
    <svg class="icon"><use href="#icon-arrow-right" xlink:href="#icon-arrow-right"/></svg>
</button>

У нас могут быть кнопки без текста, только с иконкой, поэтому стоит привязать стили к классу .icon внутри .btn и убедиться, что иконка наследует стили подписи, даже если самой подписи нет.

.btn {
    display: inline-flex;
    position: relative;
    white-space: nowrap;
    text-decoration: none;
    line-height: 1;

    padding: var(--space-xs) var(--space-sm);
    border-radius: var(--btn-radius);
    font-size: var(--btn-font-size);
    color: var(--color-link);

    transition: .2s;

    &:active {
        transition: none;
    }

    .icon {
        /* icon inherits color of button label */
        color: inherit;
        flex-shrink: 0;
    }
}

Группы кнопок

В основной CSS-файл также стоит включить стили для кнопочных групп. К счастью, Flexbox-модель позволяет легко располагать элементы в одном измерении:

/* buttons group */
.btns {
    display: flex;
    flex-wrap: wrap;
    margin-bottom: calc(-1 * var(--space-xs));

    > * {
        margin-right: var(--space-xs);
        margin-bottom: var(--space-xs);

        &:last-of-type {
            margin-right: 0;
        }
    }
}

Отзывчивые кнопки

Вся наша дизайн-система, описанная в предыдущих статьях, основана на относительных единицах em и двух CSS-переменных: --text-base-size и --space-unit. Если обновить их в медиа-запросе, сработает каскадный эффект для всех элементов, включая кнопки. Поэтому нет необходимости создавать дополнительные запросы для в файле _buttons.scss.

_buttons.scss

В итоге у нас получился вот такой глобальный SCSS-файл для кнопок:

/* -------------------------------- 

Buttons

-------------------------------- */

:root {
    --btn-font-size: 1em;
    --btn-sm: calc(var(--btn-font-size) - 0.2em);
    --btn-md: calc(var(--btn-font-size) + 0.2em);
    --btn-lg: calc(var(--btn-font-size) + 0.4em);

    --btn-radius: var(--radius);
}

.btn {
    display: inline-flex;
    position: relative;
    white-space: nowrap;
    text-decoration: none;
    line-height: 1;

    padding: var(--space-xs) var(--space-sm);
    border-radius: var(--btn-radius);
    font-size: var(--btn-font-size);
    color: var(--color-link);

    transition: .2s;

    &:active {
        transition: none;
    }

    .icon {
        /* icon inherits color of button label */
        color: inherit;
        flex-shrink: 0;
    }
}

/* themes */
.btn--primary {
    // main button
    background-color: var(--btn-primary-bg);
    color: var(--btn-primary-label);
    @include fontSmooth;

    &:visited {
        color: var(--btn-primary-label);
    }

    &:hover {
        background-color: var(--btn-primary-hover);
    }

    &:active {
        background-color: var(--btn-primary-active);
    }
}

.btn--secondary {
    // subtle version, used for secondary actions or in combo with primary/accent button
    background-color: var(--btn-secondary-bg);
    color: var(--btn-secondary-label);

    &:visited {
        color: var(--btn-secondary-label);
    }

    &:active {
        background-color: var(--btn-secondary-active);
    }
}

.btn--accent {
    // used to draw special attention to the button (e.g. destructive actions)
    background-color: var(--btn-accent-bg);
    color: var(--btn-accent-label);
    @include fontSmooth;

    &:visited {
        color: var(--btn-accent-label);
    }

    &:hover {
        background-color: var(--btn-accent-hover);
    }

    &:active {
        background-color: var(--btn-accent-active);
    }
}

/* feedback */
.btn[disabled] {
    cursor: not-allowed;
    background-color: var(--btn-disabled-bg);
    color: var(--btn-disabled-label);
    box-shadow: none;
    opacity: 0.6;

    &:visited {
        color: var(--btn-disabled-label);
    }
}

/* button size */
.btn--sm {
    font-size: var(--btn-sm);
}

.btn--md {
    font-size: var(--btn-md);
}

.btn--lg {
    font-size: var(--btn-lg);
}

.btn--full-width {
    display: flex;
    width: 100%;
    justify-content: center;
}

/* buttons group */
.btns {
    display: flex;
    flex-wrap: wrap;
    margin-bottom: calc(-1 * var(--space-xs));

    > * {
        margin-right: var(--space-xs);
        margin-bottom: var(--space-xs);

        &:last-of-type {
            margin-right: 0;
        }
    }
}

Заключение

Это последняя статья из серии, посвященной созданию дизайн-системы CodyHouse. Библиотеку компонентов целиком вы можете найти здесь.

0 комментариев

Оставить комментарий

*Доступные HTML-теги: a, abbr, blockquote, code, pre, del, i, em, strong, b, strike
*Не будет опубликован