логотип PurpleSchool
логотип PurpleSchool

Создание и использование UI Kit для Vue приложений

Автор

Олег Марков

Введение

UI Kit — это набор переиспользуемых компонентов пользовательского интерфейса, который помогает быстро и последовательно создавать интерфейсы приложений. Такой подход особенно популярен в разработке крупных Vue приложений, где важна стандартизация внешнего вида и логики элементов интерфейса. Хорошо спроектированный UI Kit облегчает понимание кода, позволяет команде работать быстрее и уменьшает вероятность появления ошибок, связанных с несовпадением интерфейсов.

Вы узнаете, как спроектировать, реализовать и интегрировать UI Kit в свой Vue проект, чтобы облегчить себе жизнь при построении однородных и красивых интерфейсов.


Что такое UI Kit и зачем он нужен

На базовом уровне UI Kit для Vue — это коллекция Vue компонентов вроде кнопок, форм, модальных окон, карточек и прочих элементов, которые визуализируют ваши идеи во всех частях приложения. Их можно собирать либо с нуля, либо использовать сторонние библиотеки и подгонять под собственный стиль.

Вот ключевые преимущества использования собственного UI Kit:

  • Консистентность визуального стиля приложения.
  • Быстрота разработки — перестают создаваться одинаковые компоненты повторно.
  • Легкость сопровождения и масштабирования.
  • Возможность кастомизации под задачи вашего бизнеса.
  • Упрощение тестирования за счёт изоляции компонентов.

UI Kit особенно полезен, если вы работаете в команде: этим вы буквально «договоритесь на берегу», как должны выглядеть и работать кнопки, поля ввода или, например, алерты на всех экранах.


Как структурировать UI Kit

Организация файлов и папок

Обычно UI Kit располагается в отдельной директории src/components/ui или src/ui-kit, чтобы вы могли легко импортировать компоненты в любом месте проекта.

src/
  components/
    ui/
      Button.vue
      Input.vue
      Modal.vue
      Card.vue
      ...
  App.vue
  main.js

Можно сделать отдельный модуль внутри монорепозитория, или даже вынести UI Kit в npm-пакет, если нужно использовать один и тот же набор компонентов в нескольких проектах.

Виды компонентов

Разделите компоненты на простые (атомы: Button, Input) и сложные (молекулы: Form, Modal с вложенными кнопками и инпутами, и т.д). Такой подход называется Atomic Design.

Пример структуры Atomic Design:

src/
  ui/
    atoms/
      Button.vue
      Input.vue
    molecules/
      FormGroup.vue
      Modal.vue
    organisms/
      AppHeader.vue
      AppFooter.vue

Базовые компоненты UI Kit для Vue

Пример кнопки — Button.vue

Смотрите, я покажу вам, как может выглядеть базовая кнопка. Код станет унифицированной точкой входа для всех кнопок в вашем проекте.

<template>
  <button
    :class="['ui-btn', variantClass, { 'ui-btn--disabled': disabled }]"
    :disabled="disabled"
    @click="handleClick"
  >
    <slot />
  </button>
</template>

<script>
export default {
  name: 'UiButton',
  props: {
    variant: {
      type: String,
      default: 'primary', // primary, secondary, danger...
    },
    disabled: {
      type: Boolean,
      default: false,
    },
  },
  computed: {
    variantClass() {
      // Сгенерим CSS класс для разных видов кнопок
      return `ui-btn--${this.variant}`;
    },
  },
  methods: {
    handleClick(event) {
      // Дополнительная обработка, если кнопка не отключена
      if (!this.disabled) this.$emit('click', event);
    },
  },
};
</script>

<style scoped>
.ui-btn { /* Базовый стиль */ }
.ui-btn--primary { background: #1976d2; color: white; }
.ui-btn--secondary { background: #eee; color: #1976d2; }
.ui-btn--danger { background: #c00; color: white; }
.ui-btn--disabled { opacity: 0.5; cursor: not-allowed; }
</style>

Обратите внимание — весь кастомный код и оформление завязаны на пропсы. Такой подход позволяет интегрировать кнопку в любое окружение: вы управляете типом кнопки через параметр variant и её состоянием через disabled.


Пример поля ввода — Input.vue

Теперь давайте посмотрим, как вынести обычное поле ввода в отдельный компонент, пригодный для любого места в вашем проекте.

<template>
  <div class="ui-input-group">
    <input
      :type="type"
      :value="value"
      :disabled="disabled"
      :placeholder="placeholder"
      @input="$emit('input', $event.target.value)"
      class="ui-input"
    />
    <span v-if="error" class="ui-input-error">{{ error }}</span>
  </div>
</template>

<script>
export default {
  name: 'UiInput',
  props: {
    value: String,
    type: {
      type: String,
      default: 'text',
    },
    placeholder: String,
    disabled: Boolean,
    error: String,
  },
};
</script>

<style scoped>
.ui-input-group { margin-bottom: 16px; }
.ui-input { border: 1px solid #ccc; padding: 8px; border-radius: 4px; }
.ui-input-error { color: #c00; font-size: 12px; }
</style>

Здесь добавлен обработчик событий @input, чтобы родитель мог слушать изменения значения поля. Пропсы error, disabled и placeholder позволяют контролировать внешний вид компонента.


Модальные окна — Modal.vue

Часто вам понадобится всплывающий слой для подтверждений или редактирования данных. Давайте разберём пример простого модального окна:

<template>
  <div class="ui-modal" v-if="visible">
    <div class="ui-modal__backdrop" @click="close" />
    <div class="ui-modal__content">
      <button class="ui-modal__close" @click="close">×</button>
      <slot />
    </div>
  </div>
</template>

<script>
export default {
  name: 'UiModal',
  props: {
    visible: Boolean, // Управлять модальным окном должен родитель
  },
  methods: {
    close() {
      this.$emit('close');
    },
  },
};
</script>

<style scoped>
.ui-modal { position: fixed; top:0; left:0; right:0; bottom:0; display:flex; align-items:center; justify-content:center;}
.ui-modal__backdrop { position:absolute; z-index:0; top:0; left:0; right:0; bottom:0; background:rgba(0,0,0,0.3);}
.ui-modal__content { position:relative; z-index:1; background:#fff; padding:24px; border-radius:8px; min-width:320px;}
.ui-modal__close { position:absolute; top:8px; right:8px; background:none; border:none; font-size:24px; cursor:pointer;}
</style>

Модалка принимает пропс visible, на саму модалку можно повесить v-if или v-show на уровне родителя. Событие close поможет вам управлять её видимостью.


Организация темы и стилей в UI Kit

Базовые переменные и тема

Сделайте единый файл с переменными для цветов, отступов, размеров шрифтов — так проще поддерживать тему. Обычно заведите отдельный файл, например, src/styles/_variables.scss (или просто css variables):

// _variables.scss
$primary-color: #1976d2;
$danger-color: #e53935;
$success-color: #43a047;
$font-base: "Roboto", Arial, sans-serif;

// Можно добавить миксины для отступов, радиусов и т.п.

В CSS появилось много поддержки нативных переменных, так что вы можете делать так:

:root {
  --color-primary: #1976d2;
  --color-danger: #e53935;
  --border-radius: 4px;
}

Интеграция стилей в компоненты

Импортируйте общий файл со стилями в main.js или в основной SCSS через @import. Тогда UI компоненты будут «знать» о глобальной теме.

// main.js
import './styles/main.scss';

Расширяемость и кастомизация UI Kit

Использование слотов для расширения

Почти в каждом компоненте предусмотрите <slot>, чтобы пользователь мог вставлять свой контент.

Пример расширенного использования слотов:

<UiButton>
  <span>
    <IconCheck /> Сохранить
  </span>
</UiButton>

Передача дополнительных атрибутов и событий

Добавьте поддержку v-bind="$attrs" и inheritAttrs: false, чтобы родительские элементы могли пробрасывать произвольные props.

<template>
  <button
    :class="['ui-btn']"
    v-bind="$attrs" // пробрасываем все неизвестные атрибуты
    @click="onClick"
  >
    <slot />
  </button>
</template>

<script>
export default {
  inheritAttrs: false,
  methods: {
    onClick(event) {
      this.$emit('click', event);
    },
  },
};
</script>

Автоматическая регистрация компонентов UI Kit

Чтобы не импортировать каждый компонент по отдельности, делается плагин:

// src/ui-kit/index.js
import UiButton from './Button.vue';
import UiInput from './Input.vue';
// ...другие импорты

const components = { UiButton, UiInput /* ... */ };

export default {
  install(Vue) {
    for (const name in components) {
      Vue.component(name, components[name]);
    }
  },
};

Теперь всего один импорт в main.js:

import Vue from 'vue';
import UiKit from './ui-kit';

Vue.use(UiKit);

Интеграция UI Kit в проект

Импорт и регистрация компонентов

Используйте UI Kit в любом месте приложения:

<template>
  <UiButton
    variant="danger"
    @click="handleDelete"
  >
    Удалить
  </UiButton>
</template>

Настройка темизации или сброса тем

Для поддержки разных тем предусмотрите динамические переменные. Например, через provide/inject или через библиотеку Vuex/Pinia можно хранить текущую тему, а затем менять CSS переменные на лету.

// Пример: смена темы через JS
document.documentElement.style.setProperty('--color-primary', '#8e24aa');

Организация документации UI Kit

Обязательно составьте или сгенерируйте живую документацию для компонентов UI Kit, используя Storybook, Styleguidist или MDX-файлы. Это покажет ваш набор компонентов в изоляции и позволит другим разработчикам учиться на примерах.


Покрытие тестами

UI Kit рекомендуется покрывать юнит-тестами (например, с помощью Jest + Vue Test Utils). Это позволяет уверенно вносить изменения, не опасаясь поломки визуальной части.

import { mount } from '@vue/test-utils';
import UiButton from '@/components/ui/Button.vue';

test('Рендер кнопки и обработка клика', async () => {
  const wrapper = mount(UiButton, {
    slots: { default: 'Текст' },
  });

  // Проверим содержимое
  expect(wrapper.text()).toBe('Текст');

  // Проверим emit события
  await wrapper.trigger('click');
  expect(wrapper.emitted().click).toHaveLength(1);
});

Работа с внешними UI Kit

Иногда нет времени делать свой набор с нуля, используйте готовые UI Kit: Vuetify, Element UI, Quasar, Ant Design Vue и другие. Но будьте готовы к кастомизации их переменных, чтобы адаптировать под свою айдентику.


Вывод

UI Kit делает ваш проект структурированным, ускоряет и стандартизирует разработку, облегчает внедрение новых фич. Подход с отделением компонентов в UI Kit применим практически в любом профессиональном Vue проекте. Старайтесь не дублировать код, придумывайте стандарт для своих компонентов и не забывайте про тесты и документацию.


Частозадаваемые технические вопросы по теме статьи и ответы на них

Вопрос 1: Как реализовать автотесты для UI компонентов, использующих slot?

Чтобы тестировать компоненты с slot, монтируйте их в тесте с нужным контентом: javascript const wrapper = mount(UiButton, { slots: { default: '<span>ОК</span>' } }); expect(wrapper.text()).toBe('ОК'); Так вы проверите и рендер содержимого внутри слота.


Вопрос 2: Как сделать так, чтобы компоненты UI Kit были доступны во всех дочерних компонентах без лишнего импорта?

Используйте глобальную регистрацию через плагин (см. пример выше с install). Это избавит от ручного импорта каждого компонента в каждом месте проекта.


Вопрос 3: Как добавить поддержку i18n (локализации) в компоненты UI Kit?

Импортируйте экземпляр вашего i18n и используйте его внутри компонентов: javascript // В компоненте {{ $t('ui.button.ok') }} Передавайте текстовые ключи через prop или slot и обрабатывайте их через $t для поддержки всех языков.


Вопрос 4: Как сделать, чтобы UI Kit работал и с TypeScript?

Все компоненты пишите с использованием defineComponent, используйте типизацию props и events через types из vue и дополняйте d.ts-файлы для экспорта интерфейсов.


Вопрос 5: Как настроить tree-shaking для большого UI Kit, чтобы подключать только нужные компоненты?

Экспортируйте каждый компонент как отдельный модуль, позволяя импортировать их поштучно: javascript import UiButton from 'my-ui-kit/Button.vue' Также используйте ES-модули для сборки пакета, чтобы ваши бандлеры могли удалять неиспользуемые части и экономить размер фронта.

Стрелочка влевоУправление пользователями и их данными в Vue приложенияхТипизация и использование TypeScript в VuejsСтрелочка вправо

Все гайды по Vue

Руководство по валидации форм во Vue.jsИнтеграция Tiptap для создания редакторов на VueРабота с таблицами во Vue через TanStackИнструкция по установке и компонентам Vue sliderУправление пакетами Vue js с помощью npmУправление пакетами и node modules в Vue проектахКак использовать meta для улучшения SEO на VueПолный гайд по компоненту messages во Vuejs5 правил использования Inertia с Vue и LaravelРабота с модулями и пакетами в VueИнструкция по работе с grid на VueGithub для Vue проектов - подробная инструкция по хранению и совместной работеНастройка ESLint для Vue проектов и поддержка качества кодаОбработка ошибок и отладка в Vue.jsИспользование Vue Devtools для отладки и мониторинга приложенийРабота с конфигурационными файлами и скриптами VueСоздание и настройка проектов Vue с помощью Vue CLI3 способа интеграции Chart.js с Vue для создания графиковРабота с Canvas во VueИнструкция по реализации календаря во VueРабота с Ant Design Vue для создания UI на Vue
Обзор и использование утилит Vue для удобной разработкиРабота с обновлениями компонента и жизненным циклом updateРазрешение конфликтов и ошибок с помощью Vue resolveИспользование query-параметров и их обработка в маршрутах VueЗагрузка и управление состоянием загрузки в VueИспользование библиотек Vue для расширения функционалаРабота с JSON данными в приложениях VueКак работать с экземплярами компонента Instance во VueПолучение данных и API-запросы во Vue.jsЭкспорт и импорт данных и компонентов в VueОбработка событий и их передача между компонентами VuejsГайд по defineEmits на Vue 3Понимание core функционала Vue и его применениеПонимание и применение Composition API в Vue 3Понимание и работа с компилятором VueКогда и как использовать $emit и call во VueВзаимодействие с внешними API через Axios в Vue
Веб приложения на Vue архитектура и лучшие практикиИспользование Vite для быстрого старта и сборки проектов на Vue 3Работа с URL и ссылками в приложениях на VueРабота с пользовательскими интерфейсами и UI библиотеками во VueОрганизация и структура исходных файлов в проектах VueИспользование Quasar Framework для разработки на Vue с готовыми UI-компонентамиОбзор популярных шаблонов и стартовых проектов на VueИнтеграция Vue с PHP для создания динамичных веб-приложенийКак организовать страницы и маршруты в проекте на VueNuxt JS и Vue 3 для SSR приложенийСоздание серверных приложений на Vue с помощью Nuxt jsИспользование Vue Native для разработки мобильных приложенийОрганизация и управление индексной страницей в проектах VueИспользование Docker для контейнеризации приложений на VueИнтеграция Vue.js с Django для создания полноценных веб-приложенийСоздание и работа с дистрибутивом build dist Vue приложенийРабота со стилями и CSS в Vue js для красивых интерфейсовСоздание и структурирование Vue.js приложенияКак исправить ошибку cannot find module vueНастройка и сборка проектов Vue с использованием современных инструментовИнтеграция Vue с Bitrix для корпоративных решенийРазработка административных панелей на Vue js
5 библиотек для создания tree view во VueИнтеграция Tailwind CSS с Vue для современных интерфейсовИнтеграция Vue с серверной частью и HTTPS настройкамиКак обрабатывать async операции с Promise во VueИнтеграция Node.js и Vue.js для разработки приложенийРуководство по интеграции Vue js в NET проектыПримеры использования JSX во VueГайд по импорту и регистрации компонентов на VueМногоязычные приложения на Vue с i18nИнтеграция FLIR данных с Vue5 примеров использования filter во Vue для упрощения разработки3 примера реализации drag-and-drop во Vue
Управление переменными и реактивными свойствами во VueИспользование v for и slot в VueПрименение v-bind для динамической привязки атрибутов в VueУправление пользователями и их данными в Vue приложенияхСоздание и использование UI Kit для Vue приложенийТипизация и использование TypeScript в VuejsИспользование шаблонов в Vue js для построения интерфейсовИспользование Swiper для создания слайдеров в VueРабота со стилями и стилизацией в VueСтруктура и особенности Single File Components SFC в VueРабота со SCSS в проектах на Vue для стилизацииРабота со скроллингом и прокруткой в Vue приложенияхПрименение script setup синтаксиса в Vue 3 для упрощения компонентовИспользование scoped стилей для изоляции CSS в компонентах Vue3 способа улучшить навигацию Vue с push()Обработка запросов и асинхронных операций в VueПередача и использование props в Vue 3 для взаимодействия компонентовПонимание и использование provide inject для передачи данных между компонентамиПередача данных между компонентами с помощью props в Vue jsУправление property и функциями во Vue.jsРабота со свойствами компонентов VueУправление параметрами и динамическими данными во VueРабота с lifecycle-хуком onMounted во VueОсновы работы с объектами в VueПонимание жизненного цикла компонента Vue js на примере mountedИспользование модальных окон modal в Vue приложенияхИспользование методов в компонентах Vue для обработки логикиИспользование метода map в Vue для обработки массивовИспользование хуков жизненного цикла Vue для управления состоянием компонентаРабота с ключами key в списках и компонентах VueОбработка пользовательского ввода в Vue.jsРабота с изображениями и их оптимизация в VueИспользование хуков жизненного цикла в VueОрганизация сеток и гридов для верстки интерфейсов на VueСоздание и управление формами в VueОрганизация файлов и структура проекта Vue.jsКомпоненты Vue создание передача данных события и emitРабота с динамическими компонентами и данными в Vue3 способа манипулирования DOM на VueРуководство по div во VueИспользование директив в Vue и их расширенные возможностиОсновы и применение директив в VueИспользование директив и их особенности на Vue с помощью defineИспользование компонентов datepicker в Vue для выбора датОрганизация циклов и итераций во VueКак работает компиляция Vue CoreСоздание и использование компонентов в Vue JSОбработка кликов и пользовательских событий в VueИспользование классов в Vue для организации кода и компонентовИспользование директивы checked для управления состоянием чекбоксов в VueГайд на checkbox компонент во VueОтображение данных в виде графиков с помощью Vue ChartСоздание и настройка кнопок в VueСоздание и настройка кнопок в Vue приложенияхРабота с lifecycle-хуками beforeCreate и beforeMount во VueИспользование массивов и методов их обработки в VueИспользование массивов и их обработка в Vue
Использование Vuetify для создания современных интерфейсов на VueИспользование transition во VueТестирование компонентов и приложений на VueРабота с teleport для управления DOM во VueПять шагов по настройке SSR в VuejsИспользование Shadcn UI компонентов с Vue для продвинутых интерфейсовИспользование router-link для навигации в Vue RouterКак использовать require в Vue для динамического импорта модулейРабота с динамическим рендерингом и виртуальным DOM на Vue.jsИспользование ref для управления ссылками и реактивностью в Vue 3Использование Vue Pro и его преимущества для профессиональной разработкиРуководство по nextTick для работы с DOMСоздание и использование компонентов с помощью Vue js и CУправление состоянием и реактивностью через inject и provideДинамическое обновление компонентов и данных на VueГлубокое изучение документации Vue и как эффективно её использоватьИспользование Crystal с Vue для разработкиИспользование вычисляемых свойств для динамического отображения данных на Vue jsОптимизация производительности и предупреждения в Vue
Открыть базу знаний