A flexible, framework‑agnostic Vue 3 button component with variants, sizes, icons, loading state, and customization utilities. Ship it in Single Page Apps or Server-Side Rendered (SSR) environments (e.g. Nuxt 3) with zero DOM assumptions.
- Features
- Installation
- Quick Start (SPA)
- Style usage
- Nuxt 3 / SSR Usage
- Component Registration Options
- Props
- Events
- Icons
- Customization (Styles / Theming)
- Icon-only & Variant Notes
- Accessibility
- SSR Notes
- Roadmap
- Development
- Contributing
- License
- Multiple visual states: success, info, warning, error, text, outlined
- Sizes: small, default, large, full-width option
- Icon support (pre-bundled SVG set via
import.meta.glob) - Icon-only and pure icon modes (
type="icon"+iconOnly) - Loading state with spinner
- Custom inline style override via
customStyle - Emits both a custom event and the native click
- Works in SPA and SSR (Nuxt 3) contexts
- Tree-shake friendly (Vue marked external in library build)
Using npm:
npm install @todovue/tv-buttonUsing yarn:
yarn add @todovue/tv-buttonUsing pnpm:
pnpm add @todovue/tv-buttonGlobal registration (main.js / main.ts):
import { createApp } from 'vue'
import App from './App.vue'
import '@todovue/tv-button/style.css'
import TvButton from '@todovue/tv-button'
createApp(App)
.use(TvButton) // enables <TvButton /> globally
.mount('#app')Local import inside a component:
<script setup>
import '@todovue/tv-button/style.css'
import { TvButton } from '@todovue/tv-button'
function onSubmit() {
console.log('Clicked')
}
</script>
<template>
<TvButton success icon="check" @click-button="onSubmit">Submit</TvButton>
</template>// main.ts
import { createApp } from 'vue'
import App from './App.vue'
import '@todovue/tv-button/style.css'
import { TvButton } from '@todovue/tv-button'
const app = createApp(App)
app.component('TvButton', TvButton)
app.mount('#app')// nuxt.config.ts
export default defineNuxtConfig({
modules: [
'@todovue/tv-button/nuxt'
]
})Create a plugin file: plugins/tv-button.client.ts (client-only is fine, or without suffix for SSR as it is safe):
import { defineNuxtPlugin } from '#app'
import TvButton from '@todovue/tv-button'
export default defineNuxtPlugin(nuxtApp => {
nuxtApp.vueApp.use(TvButton)
})Use anywhere:
<TvButton outlined icon="info">Details</TvButton>Optional direct import (no plugin):
<script setup>
import { TvButton } from '@todovue/tv-button'
</script>| Approach | When to use |
|---|---|
Global via app.use(TvButton) |
Many usages across app / design system install |
Local named import { TvButton } |
Isolated / code-split contexts |
Direct default import import TvButton from '@todovue/tv-button' |
Single usage or manual registration |
All boolean style props have two interchangeable forms: a long form (isSomething) and a short alias.
| Prop | Aliases | Type | Default | Description |
|---|---|---|---|---|
| buttonText | — | string | '' | Optional text (alternative to slot). |
| customStyle | — | object | {} | Inline style overrides ({ backgroundColor, color }). |
| icon | — | string | null | Name of bundled icon. |
| iconColor | — | string | 'white' | Declared but currently not applied (see Roadmap). |
| iconPosition | — | 'left' | 'right' | 'right' | Icon position relative to text. |
| type | — | 'button' | 'icon' | 'button' | Variant selector AND passed to native type attribute (see note below). |
| ariaLabel | — | string | '' | Accessibility label (required if no text / icon-only). |
| iconOnly | — | boolean | false | Renders only the icon (no padding/background). |
| isOutlined | outlined | boolean | false | Outlined style. |
| isSmall | small | boolean | false | Small size. |
| isLarge | large | boolean | false | Large size. |
| isSuccess | success | boolean | false | Success variant. |
| isInfo | info | boolean | false | Info variant. |
| isWarning | warning | boolean | false | Warning variant. |
| isError | error | boolean | false | Error variant. |
| isDisabled | disabled | boolean | false | Disables interaction. |
| isText | text | boolean | false | Text (minimal) style. |
| isFull | full | boolean | false | Full width. |
| isRounded | rounded | boolean | false | Rounded corners. |
| isLoading | loading | boolean | false | Shows spinner & disables. |
| isCircle | circle | boolean | false | Currently unused (placeholder). |
Note: Because
typeis bound to the native<button type="...">, usingtype="icon"produces a non-standard button attribute. This does not break rendering but is semantically incorrect in forms. A future release will introducevariantand keephtmlTypeseparate (see Roadmap).
| Event name (kebab) | Emits (camel) | Description |
|---|---|---|
click-button |
clickButton |
Custom semantic click event. |
click |
click |
Native passthrough (also emitted manually). |
Usage:
<TvButton @click-button="onAction" />
<TvButton @click="onNative" />Set with the icon prop. Available names:
account, add-user, alert, arrow-down, arrow-left, arrow-right, arrow-up, block, calendar, cancel, check, clone, dark, download, edit, external-link, favorite, filter, help, info, light, loading, lock, login, logout, menu, minus, notification, plus, remove, search, settings, share, star, todovue, unlock, update, view double-arrow-left, double-arrow-right, home, dots-vertical, eye-off, trash, upload.
Example:
<TvButton icon="check" success>Saved</TvButton>
<TvButton icon="info" iconPosition="left" outlined>Info</TvButton>Inline overrides via customStyle:
<TvButton :customStyle="{ backgroundColor: '#0f2e5b', color: '#fff' }">Branded</TvButton>Outlined variant adapts automatically:
<TvButton outlined :customStyle="{ backgroundColor: '#ff4081', color: '#fff' }">Pink Outline</TvButton>A subtle hover darkening is auto-generated when
customStyle.backgroundColorexists.
Pure icon button:
<TvButton type="icon" icon="edit" />Inline icon-only action (no background / padding):
<TvButton type="icon" icon="edit" :iconOnly="true" aria-label="Edit item" />Loading state:
<TvButton loading icon="download">Processing...</TvButton>- Always provide visible text OR
aria-label. - Mandatory: add
aria-labelwhen usingiconOnlyor when slot content is empty. - Disabled state uses both
disabledattribute and styling classes.
- No direct DOM (
window/document) access in source → safe for SSR. - Styles are now served from a separate CSS file generated by Vite (
dist/tv-button.css). You need to import it explicitly in your app (SPA or Nuxt) using@todovue/tv-button/style.css. - SVG icons are bundled via Vite's
import.meta.glob(works in Vite + Nuxt).
| Item | Status |
|---|---|
Separate type into variant + htmlType |
Planned |
Implement iconColor application |
Planned |
Implement isCircle styling |
Planned |
| Add theming API (CSS vars) | Considering |
| Add ARIA improvements for loading state | Considering |
git clone https://github.com/TODOvue/tv-button.git
cd tv-button
yarn install
yarn dev # run demo playground
yarn build # build libraryLocal demo served from Vite using index.html + src/demo examples.
PRs and issues welcome. See CONTRIBUTING.md and CODE_OF_CONDUCT.md.
MIT © TODOvue
Crafted for the TODOvue component ecosystem
