@surgeui/ds-vue

An accessible **Vue 3** component library: components, layouts and composables built with the Composition API and TypeScript, themable through CSS custom properties.
2,233 3
Install
npm install @surgeui/ds-vue
Latest Version:3.0.2
Vue:3
License:MIT
Last Updated:Oct 26, 2026
Links: GitHub  ·  npm
Maintainer: mahmoud-nbet

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.

npm License: MIT Vue 3 TypeScript

✨ 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

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, stateInfo and their …Bg variants
  • 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:

  • SuButton has type="button" by default: pass type="submit" to submit a form.
  • SuSwitch renders a <button> instead of a <div>.
  • SuDialog uses the native <dialog> element: the zIndex prop has no effect and the backdrop is ::backdrop.
  • Vue 3.5+ is required.
  • accessibility.announceToScreenReader() and accessibility.trapFocus() are deprecated in favor of announce() / useAnnouncer() and useFocusTrap().

See the CHANGELOG for the full list.

📖 Documentation

🤝 Contributing

Contributions are welcome! See the Contributing Guide.

📄 License

MIT - see the LICENSE file.


Made with ❤️ by the SurgeUI team

Related Packages

@getweini/vue

Shared behavior and styles for editable Weini UI components in Vue 3 and Nuxt.

659
@treeui/vue

Vue 3 component package for TreeUI.

7,405 0
@sewadah/selaras

A UI component library for Nuxt, built on Reka UI and Tailwind v4

2,092 0
@juxtui/ui

Accessible, polished UI components for Nuxt & Vue 3.

1,648 0
kui-vue

A lightweight desktop UI component library suitable for Vue.js 3.

37,428 2

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
Pre-releases (1)
Version Released Vue License
1.0.1-beta.0 ^3.3.0 MIT