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

Создание и настройка кнопок в Vue

Автор

Алексей Гордеев

Введение

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

В этой статье я покажу вам, как создавать и настраивать кнопки во Vue с нуля, а также расскажу о продвинутых возможностях: кастомной обработке событий, динамическом управлении внешним видом, использовании слотов, добавлении иконок и управлении состоянием загрузки.

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

Создание простой кнопки во Vue

Для начала рассмотрим, как добавить простую кнопку в шаблон компонента. Вот самый базовый пример:

<template>
  <!-- Обычная кнопка с событием click -->
  <button @click="handleClick">Нажми меня</button>
</template>

<script>
export default {
  methods: {
    handleClick() {
      // Здесь ваш код обработки нажатия
      alert('Кнопка была нажата!');
    }
  }
}
</script>

В этом примере кнопка отображается на странице, а нажатие вызывает метод handleClick. Все очень просто: используем нативный элемент <button>, добавляем событие через @click, и обрабатываем его в методах компонента.

Стилизация кнопки

Для управления внешним видом проще всего использовать стандартные CSS-классы:

<template>
  <button class="my-btn" @click="handleClick">Стилизованная кнопка</button>
</template>

<style>
.my-btn {
  background: #20b2aa;
  color: #fff;
  border: none;
  padding: 10px 16px;
  border-radius: 4px;
  font-size: 16px;
  cursor: pointer;
}

.my-btn:hover {
  background: #009688;
}
</style>

Смотрите: мы подключили стили внутри компонента. Такой подход делает стили кнопки локальными (они применяются только к этому компоненту).

Разработка универсального компонента кнопки

Часто в проекте возникает задача повторять один и тот же вид и логику кнопки в разных местах. Для этого удобно создать общий компонент, например BaseButton.vue.

Пример базового компонента

<template>
  <button
    :class="computedClass"
    :type="type"
    :disabled="disabled"
    @click="onClick"
  >
    <slot></slot> <!-- Содержимое кнопки передаётся через слот -->
  </button>
</template>

<script>
export default {
  name: 'BaseButton',
  props: {
    type: {
      type: String,
      default: 'button' // button, submit, reset
    },
    disabled: {
      type: Boolean,
      default: false
    },
    variant: {
      type: String,
      default: 'primary' // primary, secondary, danger
    }
  },
  computed: {
    computedClass() {
      return [
        'btn',
        `btn--${this.variant}`,
        { 'btn--disabled': this.disabled }
      ]
    }
  },
  methods: {
    onClick(event) {
      // Не вызываем emit, если кнопка неактивна
      if (this.disabled) return;
      this.$emit('click', event);
    }
  }
}
</script>

<style scoped>
.btn {
  font-size: 15px;
  padding: 8px 20px;
  border: none;
  border-radius: 5px;
  cursor: pointer;
}
.btn--primary {
  background: #42a5f5;
  color: #fff;
}
.btn--secondary {
  background: #fff;
  color: #333;
  border: 1px solid #bdbdbd;
}
.btn--danger {
  background: #e53935;
  color: #fff;
}
.btn--disabled, .btn:disabled {
  background: #e0e0e0 !important;
  color: #bdbdbd !important;
  cursor: not-allowed;
}
</style>

Теперь вы можете использовать такой компонент в любом месте приложения:

<BaseButton variant="danger" :disabled="isLoading" @click="deleteItem">
  Удалить
</BaseButton>

Обратите внимание, как класс кнопки зависит от переданных props: меняем variant — и меняется цветовая схема. Благодаря use-slot внутрь можно передать любой контент, включая иконки и текст.

Добавление иконок в кнопки

Давайте разберём, как добавить иконку внутри нашей кнопки. Если у вас есть SVG-иконки или используете библиотеку вроде FontAwesome, это делается так:

<BaseButton variant="secondary">
  <i class="fas fa-user"></i>
  Профиль
</BaseButton>

Вы передаёте контент внутрь слота, и он оказывается внутри кнопки. При необходимости добавьте собственные стили для корректного расположения иконки внутри кнопки:

.btn i {
  margin-right: 8px;
  font-size: 16px;
  vertical-align: middle;
}

Кнопка с индикатором загрузки

Частая задача: показать индикатор загрузки вместо текста, пока идет операция. Для этого добавим еще один prop:

<template>
  <button
    :class="computedClass"
    :type="type"
    :disabled="disabled || loading"
    @click="onClick"
  >
    <span v-if="loading"> <!-- Вместо слота покажем индикатор -->
      <svg class="spinner" viewBox="0 0 24 24">
        <circle cx="12" cy="12" r="10"
                fill="none" stroke="#fff"
                stroke-width="3" stroke-linecap="round"
                stroke-dasharray="60" stroke-dashoffset="20">
        </circle>
      </svg>
    </span>
    <span v-else>
      <slot></slot>
    </span>
  </button>
</template>

<script>
export default {
  // ...предыдущие props
  props: {
    loading: {
      type: Boolean,
      default: false
    },
    // дополнительные props...
  },
  // ...computed и methods те же
}
</script>

<style scoped>
.spinner {
  width: 18px;
  height: 18px;
  animation: spin 1s linear infinite;
  vertical-align: middle;
}
@keyframes spin {
  100% {
    transform: rotate(360deg);
  }
}
</style>

Теперь, передав :loading="true", вы спрячете содержимое слота и покажете индикатор.

<BaseButton :loading="isLoading" @click="saveData">Сохранить</BaseButton>

Расширенные возможности пользовательских кнопок

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

Иногда нужно добавить ещё атрибуты — например, aria-* для доступности или tabindex. Используйте директиву v-bind и специальный объект $attrs:

<template>
  <button
    v-bind="$attrs"
    :class="computedClass"
    @click="onClick"
  >
    <slot></slot>
  </button>
</template>

Теперь любые неявные props будут автоматически попадать в <button> вызванного компонента:

<BaseButton aria-label="Сохранить изменения" tabindex="2">
  Сохранить
</BaseButton>

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

В некоторых случаях хочется, чтобы в кнопку можно было вставлять что-то большее, чем просто текст — например, разные иконки или даже вложенные элементы. Слоты как раз для этого:

<BaseButton>
  <i class="fas fa-download"></i>
  Скачать отчёт
  <span class="badge">PDF</span>
</BaseButton>

Добавьте необходимые стили и контекстное позиционирование для внутренних элементов.

Пользовательские события и модификаторы

По умолчанию компонент кнопки (как наш BaseButton выше) «выбрасывает» (emit) только событие click. Но вы можете расширить этот список, например, добавить событие focus или blur:

<button
  @click="onClick"
  @focus="$emit('focus', $event)"
  @blur="$emit('blur', $event)"
>
  <slot></slot>
</button>

Теперь у вас есть полный контроль — вы можете ловить focus и blur во внешних компонентах.

Еще более гибкая опция — использовать модификаторы событий. Например, если вам нужно предотвратить всплытие или отменить действие по умолчанию:

<button @click.stop.prevent="onClick"><slot /></button>

В таком случае родительские события нажатия на кнопку не сработают, а также будет отменено поведение по умолчанию (например, submit в форме).

Настраиваемые стили через props

Возникает задача: делать кнопки с переменной шириной, высотой, цветом через props. Это удобно для уникальных кейсов.

<template>
  <button
    :style="computedStyle"
    @click="onClick"
  >
    <slot></slot>
  </button>
</template>

<script>
export default {
  props: {
    width: String,
    height: String,
    color: String
  },
  computed: {
    computedStyle() {
      return {
        width: this.width,
        height: this.height,
        background: this.color
      }
    }
  }
}
</script>

Теперь очень просто использовать:

<BaseButton width="120px" color="#ff9800">Оранжевая кнопка</BaseButton>

Передача и обработка value на кнопке

В HTML есть привычная особенность, когда кнопка внутри формы имеет атрибут value. Если вы хотите передать value и обработать его во внешнем компоненте при нажатии, поступаем так:

<template>
  <button :value="value" @click="onClick"><slot /></button>
</template>
<script>
export default {
  props: {
    value: {
      type: [String, Number, Object],
      default: ''
    }
  },
  methods: {
    onClick(event) {
      this.$emit('click', this.value, event)
    }
  }
}
</script>

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

<BaseButton value="42" @click="handleClick">Отправить</BaseButton>

// во внешнем компоненте:
methods: {
  handleClick(value, event) {
    // value === '42'
  }
}

Использование кнопок в формах

Кнопки часто используются для отправки форм. Во Vue для этого важно явно задать тип кнопки (submit или reset), чтобы избежать неожиданного поведения:

<form @submit.prevent="handleSubmit">
  <BaseButton type="submit">Сохранить</BaseButton>
  <BaseButton type="reset">Сбросить</BaseButton>
</form>

Когда нужен сброс формы — используйте type="reset". Если не указать тип, браузер по умолчанию считает кнопку submit, что может привести к незапланированной отправке формы.

Применение кастомных классов и динамических стилей

Если вы разрабатываете компонент, который должен быть максимально гибким, стоит позволить пользователю передавать дополнительные CSS-классы через пропсы или $attrs.

<button :class="['my-base-btn', customClass]" @click="onClick">
  <slot />
</button>

props: {
  customClass: String
}

Используйте так:

<BaseButton customClass="big-btn important-btn">Нажми на меня</BaseButton>

Теперь вы легко расширяете стили без необходимости менять внутренности компонента.

Совет: Интеграция с UI-библиотеками

Если у вас крупный проект и стандартных компонентов не хватает — посмотрите в сторону готовых решений: Element Plus, Vuetify, BootstrapVue. Они предлагают кнопки на любой вкус, зачастую с иконками, лоудерами и кастомными стилями "из коробки".

Пример с Vuetify:

<v-btn color="primary" @click="doAction">Сделать действие</v-btn>

В этом случае настройка и внешний вид контролируются самой библиотекой.

Заключение

Кнопки в Vue можно делать как простыми, так и невероятно гибкими. Основные техники — это использование props и слотов для передачи данных и содержимого, кастомизация классов и inline-стилей, управление состояниями (загрузка, disabled), обработка событий и поддержка передач дополнительных атрибутов.

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

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

Как сделать так, чтобы кнопка занимала 100% ширины родителя во Vue?

Можно использовать CSS-класс с width: 100%: vue <BaseButton customClass="full-width-btn">Широкая кнопка</BaseButton> css .full-width-btn { width: 100%; } Либо передавайте стиль прямо через prop/атрибут style="width: 100%;".

Как передать кастомные события из BaseButton в родительский компонент?

В <button @click="onClick"> методе используйте $emit('click', ...args) для пробрасывания событий. В родителе их ловите через @click.

Как сделать так, чтобы кнопка рендерилась ссылкой <a>, если передан href?

Добавьте prop href, а в шаблоне с помощью условия v-if выводите либо <button>, либо <a>. Важно прокидывать нужные атрибуты и классы обоим элементам.

Почему не работает v-model на кнопке?

v-model во Vue применяется к input-элементам, select, textarea или кастомным компонентам с реализацией pair value/emit-input, но не к <button> напрямую. Для кнопок используйте обработку событий.

Как ограничить количество быстрых кликов по кнопке (debounce/throttle)?

Можно в обработчике клика использовать debounce/throttle через lodash: javascript import { throttle } from 'lodash' methods: { throttledClick: throttle(function() { // обработка }, 1000) } И повесить @click="throttledClick" на кнопку.

Стрелочка влевоОтображение данных в виде графиков с помощью Vue ChartСоздание и настройка кнопок в Vue приложенияхСтрелочка вправо

Все гайды по 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Понимание и использование provide inject для передачи данных между компонентамиПередача и использование props в Vue 3 для взаимодействия компонентовПередача данных между компонентами с помощью 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
Открыть базу знаний