@surgeui/ds-vue
| Install | |
|---|---|
npm install @surgeui/ds-vue |
|
| Latest Version: | 3.0.2 |
| Vue: | 3 |
| License: | MIT |
| Last Updated: | Oct 26, 2026 |
| Links: | GitHub · npm |
SurgeUI Vue Design System
An accessible Vue 3 component library: components, layouts and composables built with the Composition API and TypeScript, themable through CSS custom properties.
✨ Features
- 🧩 50+ components - Display, actions, forms and 9 mobile-first layouts
- ♿ Accessible - WAI-ARIA patterns (combobox, menu button, tabs, accordion, dialog), full keyboard support, screen reader announcements
- 🎨 Themable - 4 built-in themes × light / dark mode, CSS custom properties (
--su-*) - 🌍 Translatable - Default texts in French and English, customizable messages
- 🔧 TypeScript - Typed props, emits and composables
- 📱 Responsive - Mobile first; layouts adapt to their container width (container queries)
- 🔄 RTL & LTR - Right-to-left support, including keyboard navigation
Quick Start
Requirements
- Vue 3.5+
Installation
npm install @surgeui/ds-vue
Global installation (recommended)
Registers every component with the Su prefix (SuButton, SuInputField, …):
// main.ts
import { createApp } from 'vue'
import SurgeUI from '@surgeui/ds-vue'
import '@surgeui/ds-vue/style.css'
import App from './App.vue'
createApp(App)
.use(SurgeUI)
.mount('#app')
<template>
<SuButton variant="primary">My first button</SuButton>
</template>
Selective import
Components are exported without the prefix:
<script setup lang="ts">
import '@surgeui/ds-vue/style.css'
import { Button as SuButton, InputField as SuInputField } from '@surgeui/ds-vue'
import type { ButtonProps } from '@surgeui/ds-vue'
</script>
<template>
<SuInputField label="Email" type="email" />
<SuButton variant="primary">Subscribe</SuButton>
</template>
Plugin options
import SurgeUI, { locales } from '@surgeui/ds-vue'
app.use(SurgeUI, {
prefix: 'Su', // Component prefix: SuButton, SuDialog…
locale: locales.en, // Default texts language (French by default)
theme: { // Theme overrides (see Customization)
primaryDefault: '#2563eb'
}
})
🧩 What's inside
| Category | Components |
|---|---|
| Display | Heading, Panel, Icon, Image, Avatar, AvatarGroup, Badge, Card, Alert, Tabs, Accordion, Dialog, Popover, Progress, Spinner, Price |
| Actions | Button, ButtonGroup, Link, LinkGroup, Toggle, ToggleGroup, Dropdown, FloatButton |
| Forms | Input, Textarea, Password, SelectBox, RadioGroup, CheckboxGroup, Switch, Slider, FileUpload, OtpInput, FormField, FormFieldGroup, and a *Field version of each control (label + message) |
| Layouts | SingleColumnLayout, SidebarLayout, SplitLayout, HolyGrailLayout, MagazineLayout, GridLayout, MasonryLayout, HeroLayout, FullScreenLayout |
Composables
| Composable | Purpose |
|---|---|
useTheme |
Theme and light / dark mode, system preference, persistence |
useCustomTheme |
Runtime CSS variable overrides |
useBreakpoint |
Reactive breakpoints |
usePrice |
Localized price formatting |
useLocale |
Default component texts (provideLocale, defineLocale) |
useFormField |
Links a custom control to SuFormField (id, label, message, states) |
useAnnouncer |
Screen reader announcements through persistent live regions |
useFocusTrap |
Stacked focus traps for panels and modal popovers |
useDismiss |
Layered dismissal: Escape, pointer down outside, focus loss |
useListNavigation / useTypeahead |
Arrow keys, Home / End and typeahead for lists, menus and tabs |
useFloatingPosition |
Positions a floating element against its trigger (flip, shift, arrow) |
📝 Forms
A control placed in SuFormField links itself to the field: label, help message, aria-describedby, error state, required and disabled. No slot props needed:
<SuFormField label="Country" message="Country of residence" state="error" required>
<SuSelectBox :options="countries" v-model="country" />
</SuFormField>
The *Field components (SuInputField, SuSelectBoxField…) bundle the field and the control in one component.
🎨 Themes and dark mode
Four themes (default, ocean, forest, sunset), each in light and dark mode. They are applied through the data-theme and data-theme-mode attributes on the root element; useTheme sets them for you, follows the system preference and remembers the user's choice:
<script setup>
import { useTheme } from '@surgeui/ds-vue'
const { setTheme, setThemeMode, toggleMode, isDarkMode } = useTheme()
</script>
<template>
<SuButton @click="toggleMode">{{ isDarkMode ? 'Light' : 'Dark' }} mode</SuButton>
<SuButton @click="setTheme('ocean')">Ocean theme</SuButton>
</template>
setThemeMode accepts 'light', 'dark' or 'system'. Without useTheme, set the attributes yourself:
<html data-theme="default" data-theme-mode="dark">
🎨 Customization
Theme configuration
Theme properties passed to the plugin become CSS variables with the --su-custom- prefix, which override the default tokens:
app.use(SurgeUI, {
theme: {
textPrimary: '#1f2937', // → --su-custom-text-primary
bgSurface: '#f9fafb', // → --su-custom-bg-surface
primaryDefault: '#3b82f6', // → --su-custom-primary-default
primaryHover: '#2563eb',
stateError: '#ef4444'
}
})
CSS variables
You can also define the variables directly, globally or on a section of the page:
:root {
--su-custom-primary-default: #3b82f6;
--su-custom-primary-hover: #2563eb;
--su-custom-primary-text: #ffffff;
}
/* Values for the dark mode */
[data-theme-mode='dark'] {
--su-custom-bg-canvas: #111827;
--su-custom-bg-surface: #1f2937;
}
/* A section with its own colors */
.promo {
--su-custom-primary-default: #db2777;
}
Priority: --su-custom-* variables (CSS or plugin theme option) → theme tokens.
Available theme keys
- Text -
textPrimary,textSecondary,textTertiary,textDisabled,textInverse - Links -
linkDefault,linkHover,linkVisited,linkMuted - Backgrounds -
bgCanvas,bgSurface,bgSurfaceElevated,bgOverlay,bgDisabled,bgHover,bgActive,bgSelected - Borders -
borderDefault,borderSubtle,borderStrong,borderFocus,borderDisabled - Primary actions -
primaryDefault,primaryHover,primaryActive,primaryDisabled,primaryText - Secondary actions -
secondaryDefault,secondaryHover,secondaryActive,secondaryDisabled,secondaryText - States -
stateSuccess,stateWarning,stateError,stateInfoand their…Bgvariants - Shadow -
shadowColor
🌍 Languages
Default component texts (accessible labels, placeholders, error messages, announcements) are in French; English is provided. An explicit prop always wins.
import SurgeUI, { defineLocale, locales } from '@surgeui/ds-vue'
app.use(SurgeUI, {
locale: defineLocale(locales.en, {
messages: { selectBox: { placeholder: 'Pick one…' } }
})
})
The locale option also accepts a ref, and provideLocale() switches the language of a subtree at runtime.
🖼️ Icons
Components accept any Vue component as an icon; the examples use Heroicons:
<script setup>
import { HeartIcon } from '@heroicons/vue/24/outline'
</script>
<template>
<SuButton variant="primary" :icon="HeartIcon" icon-display="left">I like it</SuButton>
</template>
♿ Accessibility
Components are built to meet WCAG 2.1 AA:
- WAI-ARIA patterns - combobox + listbox (SelectBox), menu button (Dropdown), tabs, accordion, modal dialog (native
<dialog>) - Keyboard - arrow keys, Home / End, typeahead, Escape; RTL-aware arrows
- Focus - visible focus rings, focus returned to the trigger, focus trap for modal popovers
- Forms - labels, messages and states linked automatically to the control
- Screen readers - persistent live regions for announcements, named landmarks (HolyGrailLayout, with skip link)
- Preferences -
prefers-reduced-motion,prefers-contrast: more, minimum touch targets
⬆️ Upgrading to 3.0
Version 3.0 contains breaking changes:
SuButtonhastype="button"by default: passtype="submit"to submit a form.SuSwitchrenders a<button>instead of a<div>.SuDialoguses the native<dialog>element: thezIndexprop has no effect and the backdrop is::backdrop.- Vue 3.5+ is required.
accessibility.announceToScreenReader()andaccessibility.trapFocus()are deprecated in favor ofannounce()/useAnnouncer()anduseFocusTrap().
See the CHANGELOG for the full list.
📖 Documentation
- Documentation - Guides, component API, live examples (French and English)
- Storybook - Interactive examples and accessibility checks
- GitHub Repository
🤝 Contributing
Contributions are welcome! See the Contributing Guide.
📄 License
MIT - see the LICENSE file.
Made with ❤️ by the SurgeUI team
Related Packages
Version History
| Version | Released | Vue | License |
|---|---|---|---|
| 3.0.2 | ^3.5.0 | MIT | |
| 3.0.1 | ^3.5.0 | MIT | |
| 3.0.0 | ^3.5.0 | MIT | |
| 2.3.0 | ^3.3.0 | MIT | |
| 2.2.0 | ^3.3.0 | MIT | |
| 2.1.0 | ^3.3.0 | MIT | |
| 2.0.0 | ^3.3.0 | MIT | |
| 1.2.0 | ^3.3.0 | MIT | |
| 1.1.0 | ^3.3.0 | MIT | |
| 1.0.5 | ^3.3.0 | MIT | |
| 1.0.3 | ^3.3.0 | MIT | |
| 1.0.2 | ^3.3.0 | MIT | |
| 1.0.0 | ^3.3.0 | MIT |
Pre-releases (1)
| Version | Released | Vue | License |
|---|---|---|---|
| 1.0.1-beta.0 | ^3.3.0 | MIT |