@vibrant-wellness/va-responsive-components-library
v0.2.5
Published
A comprehensive Vue 3 component library built with modern development practices, providing reusable UI components, composables, and directives for building robust web applications.
Readme
VA Responsive Components Library
A comprehensive Vue 3 component library built with modern development practices, providing reusable UI components, composables, and directives for building robust web applications.
🚀 Features
- 32 Production-Ready Components - Form controls, layout, navigation, feedback, data-display, and utility components
- 3 Powerful Composables - Reusable logic for common patterns
- 2 Custom Directives - Loading states and UI enhancements
- Vue 3 Composition API - Modern, performant, and type-safe
- Fully Responsive UI & UX - Components automatically adapt to all screen sizes with optimized mobile-first design and touch-friendly interactions
- Accessibility - ARIA attributes and keyboard navigation support
- Customizable Themes - Multiple built-in color schemes
Newly released public components:
DatePicker,FileUploader,DropdownMenu,Badge,Tab,FloatingActionButton,DynamicColorResponsiveButton,FoldableButton,Snackbar,Notification,Breadcrumb,Toggle,NumberInput.
🆕 What's New
2026-11-04
MultiSelector— two trigger options for search-first pickers (e.g. picking orders by accession id), both off by default:- New
selection_displayprop:'chips'(default, unchanged) or'text'— the picked labels read as plain comma-separated text while closed (one ellipsised line); the search input replaces it while open.collapse_thresholddoes not apply in text mode. - New
trigger_iconprop:'chevron'(default, unchanged) or'search'— a static magnifier in the ink text colour instead of the down-arrow that flips while open.
- New
2026-08-27
Label:- Default label text weight is now 350 (Book) — previously 500.
- New
font_size/font_weightprops (number = px, or any CSS string; defaultnull= stylesheet's 14px / 350). The red(Required)marker keeps its own 14px size but inherits a customfont_weight.
SingleSelector— the clear affordance is now the same "Clear"TextButtontheTextInputrenders (was a legacy ×-in-circle icon button). Same visibility rules (clearable+ value + hover/focus) and the sameclearevent.
2026-08-20
MultiSelector— collapsed-summary fixes:- The
collapse_thresholdsummary ("X selected") is now rendered with the sameChipcomponent as the regular selected chips (neural-ocean, filled) instead of a bespoke<span>. - Fixed narrow triggers in collapse mode: opening the dropdown no longer pushes the summary chip's left edge out of view. The summary row stays on a single line — the chip never shrinks, and the open-state search input takes only the remaining space instead of forcing an 80px minimum (which used to overflow and, once focused, scroll the chip out of view).
- The
DatePicker:- New
disable_future_datesprop (mirror ofdisable_past_dates) — whentrue, every date after today is disabled; today stays selectable. Works in single and range modes. - Trigger heights now follow the selectors' convention — the border sits on top of the content height:
small34px,medium42px,large50px rendered (content + 1px×2 border), previously 32/40/48 total.
- New
2026-07-28
MultiSelector— newteleportprop (defaulttrue, backward-compatible). Set:teleport="false"to render the dropdown inline (in normal flow directly below the trigger) instead of teleporting it to<body>: it pushes following content down and lets an ancestor container (e.g. a dialog) grow to wrap it, with no fixed positioning or re-position-on-scroll. Intended for modals/cards where the floating dropdown would overflow or need manual re-positioning.
2026-07-08
NumberInput— new numeric stepper input with minus/plus buttons,min/maxclamping, configurablestep, 3 sizes, and helper text withenable/warning/errorstates (erroralso recolors the buttons). Minus/plus buttons now have a fast (0.15s) color transition on hover/active.
2026-06-24
- Rebrand — new brand fonts, type scale, and color palette. (See the Style Guide for full details.)
- Self-hosted brand fonts, base64-inlined into the bundled CSS (zero asset-path config for consumers):
ABC Oracle— primary sans, full variable weight axis (Thin 100 → Ultra 950) plus italic cuts. New token--font-family-brand; the global--font-familynow defaults to ABC Oracle (Inter kept as fallback).GT Pressura Mono— mono accent, 5 weights + italics, plus the opticalGT Pressura Mono Textcut for small sizes. New token--font-family-mono.
- Rebranded type scale — utility classes named
type-<category>-<tier>-<weight>-<size>for ABC Oracle (e.g.type-display-large-semibold-56,type-body-medium-regular-14) andtype-mono-…for GT Pressura Mono body sizes (e.g.type-mono-body-regular-regular-14). Oct-2026 spec addstype-display-xlarge-bold-76and the 48px display pair, and scopes GT Pressura Mono to 16/14/12/10px (older mono display/title classes are legacy). - Rebranded color palette — brand
Blue/Grey/Blackplus 10 accent colors, each with surface ramps<Name>_W_<pct>(over white) and<Name>_B_<pct>(over black), e.g.--Blue_W_50,--Cardio_Rust,--Sky_B_80. New state tokens--Error_Red,--Error_Red_Hover,--Error_Red_Pressed,--Light_Gray. - Legacy
EHR_*typography classes and existing color tokens (--TrueBlack,--VibrantDarkBlue,--Chip-*,--ErrorRed*, gradients, …) remain available for backward compatibility.
- Self-hosted brand fonts, base64-inlined into the bundled CSS (zero asset-path config for consumers):
Radio— recolored to the rebranded palette.built_in_themevalues are nowdark(#012171),theme-blue(#0142E2), andmedium(#0135B5); error uses#D10000and disabled#A2A2A2. The oldmedium-blue/medium-greenvalues still work as aliases (→theme-blue/medium) — notemedium-greennow renders blue, as the rebrand removed green.
2026-06-18
Toggle— new binary on/off switch component:- 3 color themes:
dark(VibrantDarkBlue),sky(New_Library_PressBlue),teal_green(NewVibrantGreen). - 2 styles when there's no preference set:
filled(solid colored track) andoutlined(hollow track with theme-colored border + thumb). - 2 sizes:
large(48×26 track / 22 thumb) andsmall(40×22 track / 18 thumb). - Preference mode — set
preference="on"orpreference="off"to lock the preferred state to the filled style and the opposite state to outlined, so flipping the toggle visually moves between the two styles. Matches the Figma "Preference Toggles" matrix. mirroredprop — flips the layout horizontally; thumb moves in the opposite direction.- Customizable ON/OFF labels (
on_label,off_label) or hide them entirely (show_labels=false). - Two-way bound via
v-model. Emitschangealongside. - Accessible: rendered as
role="switch"witharia-checked/aria-disabledand keyboard activation via Space / Enter.
- 3 color themes:
2026-06-17
- Public release of
DatePicker— calendar-based date input with a teleported dropdown, supporting single-date and date-range (dual-calendar) modes. Follows the Figma DatePicker design.- Three sizes (
small32px,medium40px,large48px),disabledstate, and configurable displayformat('MMM-DD-YYYY','YYYY-MM-DD','MM/DD/YYYY'). valueTypeprop controls the bound value type:'utc-string'(default, ISO 8601),'date'(nativeDate), or'timestamp'(Unix ms).rangemode shows two calendars side by side; the right panel is always kept at least one month ahead of the left, and the panels auto-shift when navigating would violate that.full_widthprop — the trigger box stretches to fill its container (default width is280px).disable_past_datesprop — disables every date before today (today stays selectable); works in both single and range modes.
- Three sizes (
2026-06-05
FileUploader— enhanced with single image upload functionality:single_img_uploadprop — enables image-only upload mode with visual previewsingle_img_typeprop — choose between'images'(centered preview with hover delete) or'icon'(logo-style with corner delete)full_widthprop — uploader takes 100% width of its containerdrop_hint_singleandbutton_text_singleprops — customize single image mode messaging- Improved image preview with proper centering and overflow handling
FileUploadModal— new modal wrapper for FileUploader:- Searchable, paginated file table driven by a
rowsprop - Newly added files are staged internally and delivered via
@submit - Inline rename, delete (with immediate removal for staged rows), and readonly mode
- Open/close controlled via standard
v-model
- Searchable, paginated file table driven by a
2026-05-11
Pagination— added two template-string props for i18n:per_page_label(default'{number} per page') — template for each per-page option label. Supports the{number}placeholder. e.g.'1ページあたり{number}件'.summary_template(default'') — full template for the left summary text. Supports{start},{end}, and{total}placeholders. When non-empty it overrides the default English summary entirely anditem_labelis ignored. e.g.'{total}件中 {start}〜{end}件を表示'.
Repeater— forwards bothper_page_labelandsummary_templateto the innerPagination, so the same i18n strings work on the table-with-pagination case.Repeater— addedcustom_header_classprop. Applied to the column header row AND each default header text span, so a single user-defined class can overridefont-size,font-weight,color, andbackground-colorat once. Use Vue's:deep(.my-header) { ... }inside<style scoped>(or:global(), or an unscoped stylesheet) — a plain scoped selector won't match because Repeater's internals carry a different scoping ID.
2026-05-08
Tab— addedequal_widthprop. Whentrue, the tab bar fills 100% of its container and tabs share that width equally (flex: 1 1 0; long labels ellipsis). For full-width header bars with no trailing space and no separate right-side tab. Ignored forverticaland whenindie_tabis set. Overridestab_widthwhen both are passed.
2026-05-07 (v0.0.2)
ResponsiveButton— addedis_activeprop: renders with disabled-like grey styling whenfalse, but stays fully clickable and continues to emitclick-button.- Package renamed to
@vibrant-wellness/va-responsive-components-library. Update yourpackage.jsonand imports to use the new scoped name. FoldableButton— refined padding/layout for tighter alignment.Dialog— addedheaderIconWidthandheaderIconHeightprops to control the header icon container size (default'24px'). AddedheaderBackgroundColorandfooterBackgroundColorprops for custom header/footer background colors.AreaCodePhoneInput—focusstate now only tracks the phone number input, not the country selector.- Documentation now covers
Tooltip,Pagination,InlineNotification,Dialog,StepAccordion,ProgressIndicator, andRepeater(all previously registered but undocumented).
2026-05-01
Tab— responsive sizing props added. Tab can now adapt to any container width with fine-grained control:width— total group width (Number → px, or any CSS length string)tab_width— fixed per-tab width (e.g. EHR ez-bill main-tab135px)font_size,icon_size— independent label/icon scalinggap,padding— per-tab spacing for compact / spacious presetsgroup_padding— horizontal padding on the group container itself (overrideoutlined/filleddefault0 24px, e.g. set0so tabs span the full configuredwidth)overflow_mode—'scroll'(default, hidden horizontal scroll) or'ellipsis'(tabs shrink, labels truncate in place)- Default: tabs keep their natural content width and the main row scrolls horizontally if content exceeds the container; labels never get aggressively cropped unless explicitly opted in.
2026-04-23
- Public release of:
FileUploader— fully-controlled file uploader with drag-and-drop, a 7-state row status machine (default / uploading / pending-success / pending-failure / failed / hide / hidden), and parent-driven progress. Follows the Figma File Uploader design.
2026-04-22
- Public release of:
Breadcrumb
2025-10-28
- Public release of:
DropdownMenuBadgeFloatingActionButtonDynamicColorResponsiveButton
Import options:
- Global (plugin):
app.use(VaResponsiveComponentsLibrary)then use components directly in templates.
- Named import:
import { DropdownMenu, Badge, FloatingActionButton, DynamicColorResponsiveButton, FoldableButton } from '@vibrant-wellness/va-responsive-components-library'
📦 Installation
npm install @vibrant-wellness/va-responsive-components-libraryor
npm install @vibrant-wellness/va-responsive-components-library@latest🎯 Quick Start
1. Install and Setup
First, import and register the library in your main.js:
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import VaResponsiveComponentsLibrary from '@vibrant-wellness/va-responsive-components-library'
import '@vibrant-wellness/va-responsive-components-library/style.css'
const app = createApp(App)
app.use(VaResponsiveComponentsLibrary)
app.mount('#app')2. Use Components
<template>
<div>
<!-- Basic checkbox -->
<Checkbox v-model="isChecked" label="Accept terms" />
<!-- Phone input with country selection -->
<AreaCodePhoneInput v-model="phoneData" />
<!-- Dynamic color responsive button (Recommended) -->
<DynamicColorResponsiveButton
display_name="Click me"
button_type="filled"
built_in_theme="primary"
@click-button="handleClick"
/>
<!-- Legacy button (Deprecated but still works; not maintained). New projects should migrate to DynamicColorResponsiveButton. -->
<ResponsiveButton @click="handleClick">
Click me
</ResponsiveButton>
</div>
</template>
<script setup>
import { ref } from 'vue'
const isChecked = ref(false)
const phoneData = ref({ area_code: '', phone_number: '' })
const handleClick = () => {
console.log('Button clicked!')
}
</script>📚 Components
Form Components
AreaCodePhoneInput
International phone number input composed of the rebranded SingleSelector (country flag + dial code, 194px wide on desktop / 128px below 768px) and TextInput (phone number) 8px apart, both underline-only and sharing one size. A single pressura Label spans both fields, and help / error / alert messages render below the row via the shared InputMessage rows; the error / alert accent is applied to both underlines, the label and the message together. Features automatic country flag display, a searchable country list (type a country name, two-letter code or dial code once it is open), a phone field that only accepts phone characters (digits, space, + - ( ) .) with the phone keypad on mobile, and smart focus management.
Sizes: large → 42px fields / 16px text / 14px label; medium → 36px / 14px / 12px; small → 30px / 14px / 12px.
Attributes
| Attribute | Description | Type | Default |
|-----------|-------------|------|---------|
| v-model / modelValue | binding value - object containing area_code (string) and phone_number (string) | {area_code: string, phone_number: string} | () => ({}) |
| country_options | custom country list with country_name and country_code properties (country_code is 2-byte uppercase, e.g. 'US') | Array<{country_name: string, country_code: string}> | () => [] |
| enable_backup_country_options | use built-in country list when no custom options provided | boolean | true |
| disabled | whether input is disabled | boolean | false |
| size | size of the country selector and phone input (see Sizes above) | 'large' \| 'medium' \| 'small' | 'medium' |
| clearable | show the Clear button on the phone input when it has a value | boolean | false |
| label | label text spanning both fields, rendered by the Label component (pressura font) | string | '' |
| required | show the required marker next to the label | boolean | false |
| required_text | the required marker text | string | '*' |
| layout | 'vertical' (label above) or 'horizontal' (label left of the row) — forwarded to Label | 'vertical' \| 'horizontal' | 'vertical' |
| label_width | horizontal layout only: fixed width of the label column (number = px) — forwarded to Label | number \| string | null |
| helper_info | help message (info icon) displayed below the row | string | '' |
| helper_action_text | optional action link after the help message (emits helper-action-click) | string | '' |
| error_wording | error message with the error icon, stacked beneath any help message; puts both fields and the label in the error state | string | '' |
| error_action_text | optional action link after the error / alert message (emits error-action-click) | string | '' |
Events
| Event | Description | Parameters |
|-------|-------------|------------|
| update:modelValue | syncs v-model when the binding value changes | {area_code: string, phone_number: string} |
| change | triggers when the binding value changes | {area_code: string, phone_number: string} |
| focus | triggers when any part of the input gains focus | {area_code: string, phone_number: string} |
| blur | triggers when the entire input loses focus | {area_code: string, phone_number: string} |
| clear | triggers when clear button is clicked | {area_code: string, phone_number: string} |
| helper-action-click | the help message's action link clicked | () |
| error-action-click | the error / alert message's action link clicked | () |
Exposes
| Method | Description | Type |
|--------|-------------|------|
| focus | focus the appropriate input (country selector if no country selected, phone input if country selected) | () => void |
| blur | blur both country selector and phone input | () => void |
| clear | clear both country selection and phone number | () => void |
| alert | show alert message below the row (Rust_W_70 accent on both fields) | (message: string) => void |
| error | show error message below the row (Error_Red_Hover accent on both fields) | (message: string) => void |
| removeAlertOrErrorEffect | clear alert/error state and message | () => void |
<template>
<AreaCodePhoneInput
v-model="phoneData"
size="large"
label="Phone"
required
helper_info="Here is help info"
@change="handlePhoneChange"
/>
</template>DatePicker
Rebranded underline date field (Figma "Date Picker", node 3484-2482). The shared pressura Label sits 2px above a 40px underline field (1px Black_W_70, 1.75px Black on hover/open, label switches to Medium) with the value or placeholder on the left and a 24px calendar icon on the right. Clicking the field or the label opens a teleported light-grey card calendar (#F7F7F7, 10px radius, white hairline, soft shadow) aligned to the field's left edge 2px below it, or above the whole unit when there is no room below. Supports single-date and date-range (dual-calendar, 592px panel, white in-range band) modes, three sizes, disabled state, a configurable display format, full-width layout, and disabling past and/or future dates. The bound value type is configurable via valueType (ISO UTC string, native Date, or Unix timestamp). Parent-driven v-model changes only update the display and never emit. Esc closes the calendar and refocuses the field; a single-date panel is at most 340px wide even on a full-width field, and below a 608px viewport the range panel stacks its two calendars.
Attributes
| Attribute | Description | Type | Default |
|-----------|-------------|------|---------|
| v-model / modelValue | Bound value. Single mode: a Date / number / string matching valueType. Range mode: an array of two such values. | Date \| number \| string \| Array \| null | null |
| valueType | Controls the type emitted/bound: 'utc-string' (ISO 8601 UTC), 'date' (native Date), 'timestamp' (Unix ms). Does not affect the displayed label. | 'date' \| 'timestamp' \| 'utc-string' | 'utc-string' |
| range | Enable date-range mode — shows two calendars side by side. The right panel is always kept at least one month ahead of the left. | boolean | false |
| placeholder | Placeholder text shown when no date is selected. | string | 'Select date' |
| size | 'large' (42px field / 16px text / 14px label), 'medium' (40px / 14px / 12px, the Figma spec), 'small' (30px / 14px / 12px). | 'large' \| 'medium' \| 'small' | 'medium' |
| label | Label text rendered by the shared Label component (pressura font); empty = no label row. Clicking it opens the calendar. | string | '' |
| required | Show the required marker after the label. | boolean | false |
| required_text | The required marker text. | string | '*' |
| layout | 'vertical' (label above) or 'horizontal' (label left of the field), forwarded to Label. | 'vertical' \| 'horizontal' | 'vertical' |
| label_width | Horizontal layout only: fixed label column width (number = px). | number \| string | null |
| disabled | Disable the field (Black_W_30 underline, muted text and label) and prevent opening the calendar. | boolean | false |
| format | Display format for the trigger label only (does not affect the emitted value). | 'MMM-DD-YYYY' \| 'YYYY-MM-DD' \| 'MM/DD/YYYY' | 'MMM-DD-YYYY' |
| show_other_month_days | Show leading/trailing days from adjacent months in the calendar grid. | boolean | true |
| full_width | When true, the field stretches to fill its parent container instead of the default 340px width. | boolean | false |
| disable_past_dates | When true, all dates before today are disabled and cannot be selected (today stays selectable). | boolean | false |
| disable_future_dates | When true, all dates after today are disabled and cannot be selected (today stays selectable). | boolean | false |
| disabled_date | Disable arbitrary days (weekends, holidays…). Receives each day as a native Date at local midnight; return true to disable. Combines with the past/future props. | (date: Date) => boolean | null |
| range_block_disabled | Range mode only: forbid ranges that contain a disabled day. Once the start is picked, every day beyond the nearest disabled day on either side becomes unselectable. | boolean | false |
Events
| Event | Description | Parameters |
|-------|-------------|------------|
| update:modelValue | emitted on date selection (use with v-model); payload type matches valueType | (value: Date \| number \| string \| Array) |
| change | emitted after the selection completes and the dropdown closes; payload type matches valueType | (value: Date \| number \| string \| Array) |
Slots
| Name | Description |
|------|-------------|
| label_icon | optional 24px icon before the label text (forwarded to Label's #icon) |
Exposes
| Method | Description | Type |
|--------|-------------|------|
| open | open the calendar (no-op when disabled) | () => void |
| close | close the calendar; an unfinished range pick is discarded | () => void |
Usage
<template>
<!-- Single date (default valueType 'utc-string' → ISO 8601 string) -->
<DatePicker v-model="date" label="Date of service" :required="true" placeholder="e.g Mar-21-2026" />
<!-- Date range — v-model is an array of two values -->
<DatePicker v-model="dateRange" :range="true" placeholder="Select date range" />
<!-- Full width + disable past dates + native Date value -->
<DatePicker
v-model="bookingDate"
valueType="date"
:full_width="true"
:disable_past_dates="true"
/>
<!-- Range limited to today and earlier (e.g. a service-date filter) -->
<DatePicker v-model="serviceDateRange" :range="true" :disable_future_dates="true" />
</template>
<script setup>
import { ref } from 'vue';
import { DatePicker } from '@vibrant-wellness/va-responsive-components-library';
const date = ref(null); // '2026-03-22T12:00:00.000Z'
const dateRange = ref([]); // ['2026-03-01T...Z', '2026-03-22T...Z']
const bookingDate = ref(null); // Date { ... }
const serviceDateRange = ref([]); // only today and earlier are selectable
</script>NumberInput
Numeric stepper input with minus/plus buttons flanking a text field. Supports min/max clamping, a configurable step (optionally strict via step_strictly), three sizes, disabled state, and helper text with enable/warning/error styling (error also recolors the minus/plus buttons). Value can also be adjusted via Arrow Up/Down keys or by typing directly. v-model stays live as you type — every valid keystroke emits an already-clamped update:modelValue (so a parent reading the bound value at any moment, e.g. a Save button, never sees a stale number) — while change and the field's full internal "settle" only happen on blur, Enter, or a stepper click.
Attributes
| Attribute | Description | Type | Default |
|-----------|-------------|------|---------|
| v-model / modelValue | bound numeric value | Number | 0 |
| min | lower bound; pass null for no lower bound | Number | null |
| max | upper bound; pass null for no upper bound | Number | null |
| step | amount added/subtracted per increment/decrement | Number | 1 |
| step_strictly | when true, the value is always snapped to the nearest multiple of step (measured from min when set, otherwise from 0) — applies to typed/pasted text and externally-bound values, not just the stepper buttons | Boolean | false |
| size | control size | 'small' \| 'medium' \| 'large' | 'medium' |
| disabled | disable the field and both buttons | Boolean | false |
| helper_info | helper text shown below the control | String | '' |
| show_helper_info | whether to render the helper text | Boolean | true |
| helper_info_type | helper text color; 'error' also recolors the minus/plus buttons red | 'enable' \| 'warning' \| 'error' | 'enable' |
Events
| Event | Description | Parameters |
|-------|-------------|------------|
| update:modelValue | emitted with the clamped value on every valid keystroke, AND whenever the value is committed (button click, Enter, or blur) | (value: Number) |
| change | emitted only when the value settles (button click, Enter, or blur) and actually differs from the last settled value — not on every keystroke | (value: Number) |
| focus | emitted when the text field gains focus | — |
| blur | emitted when the text field loses focus (after the value is committed/clamped) | — |
Usage
<template>
<NumberInput v-model="quantity" :min="0" :max="10" size="medium" />
<!-- Custom step, no upper bound -->
<NumberInput v-model="amount" :min="0" :step="5" />
<!-- Error state with helper text -->
<NumberInput
v-model="quantity"
:min="1"
:max="5"
helper_info="Maximum 5 allowed"
helper_info_type="error"
/>
</template>
<script setup>
import { ref } from 'vue';
import { NumberInput } from '@vibrant-wellness/va-responsive-components-library';
const quantity = ref(1);
const amount = ref(0);
</script>Navigation Components
Breadcrumb
Navigation breadcrumb component that displays a hierarchical path of items, separated by a black / (Figma "Breadcrumb", nodes 4206-33436 / 4206-33461). All items are ABC Oracle Book 14px with 2px × 4px padding: earlier levels are brand-blue links that gain a 1px blue underline on hover, and the current (last) item is black with a 1px black underline. The weight never changes between states, so the row never reflows. An optional back arrow can lead the first item. Supports an ABC or GT Pressura Mono (uppercase) typeface via type. Each item is a <button>: keyboard users can Tab through the trail (blue focus ring) and press Enter or Space to fire item-click; the current item has aria-current="page", and the navigation landmark is only announced when stack has items. Items are tracked by position, so id values may repeat or be missing.
Attributes
| Attribute | Description | Type | Default |
|-----------|-------------|------|---------|
| stack | breadcrumb items array - each item should have id and name properties | Array<{id: string \| number, name: string}> | [] |
| type | typeface variant: 'abc' (ABC Oracle, the rebranded spec) or 'pressura' (GT Pressura Mono, uppercase) | 'abc' \| 'pressura' | 'abc' |
| show_back_icon | show a 20px back arrow before the first item; clicking it is the same as clicking the first item | boolean | false |
Events
| Event | Description | Parameters |
|-------|-------------|------------|
| item-click | triggers when any breadcrumb item is clicked or activated with Enter / Space, including the current (last) item | (item: {id: string \| number, name: string}, index: number) |
Usage
<template>
<Breadcrumb
:stack="breadcrumbStack"
@item-click="handleBreadcrumbClick"
/>
</template>
<script setup>
import { ref } from 'vue'
const breadcrumbStack = ref([
{ id: 'home', name: 'Home' },
{ id: 'products', name: 'Products' },
{ id: 'electronics', name: 'Electronics' },
{ id: 'current', name: 'Current Page' }
])
const handleBreadcrumbClick = (item, index) => {
console.log(`Clicked: ${item.name} at index ${index}`)
// Navigate to the clicked item
}
</script>Theme/Behavior Notes
Visual Styling:
- Enabled (non-current) items use
var(--Blue)(#0142E2), ABC Oracle Book 350, 14px; clickable with pointer cursor. On hover they darken tovar(--Blue_B_80)(#0135B5) and switch to Medium 500 (no underline). - Current item (last in stack) uses
var(--Black)(#161616), Medium 500, with underline decoration - Separator is a
/glyph (16px,var(--Black)) rendered in the brand font type="pressura"switches items to GT Pressura Mono, uppercase (Text→Medium on hover/active)
Interaction Rules:
- Only non-current items emit
item-clickevents - Current item is visually distinct but does not emit click events
- Each item must have a unique
idand a displayname
Tab
Tab group component with 7 type variants: outlined, filled, underline, button, vertical, capsule, and oval. Supports optional icons, badge indicators (dot, number, 99+ overflow, text pill), an independent separated tab, and v-model selection.
Icons must be extracted to standalone SVG files. This component library never inlines
<svg>markup in templates — every icon used in atab.icon(orindie_tab.icon) field is a separate.svgasset that youimportand pass as a string path. Don't paste raw SVG markup into theiconfield. If you don't yet have the icon as a file, save the SVG to@/assets/icons/<name>.svgfirst, thenimport iconX from '@/assets/icons/<name>.svg'and referenceiconX. This keeps the icon set auditable, lets the build cache & deduplicate assets, and is required for the auto dark-bg invert (filter: brightness(0) invert(1)) to work uniformly across variants. To bypass the inversion or render custom markup, use the#icon-{index}/#icon-indieslots — that is the only sanctioned place for inline SVG.Icon color on dark backgrounds: for variants with a dark selected background (
filledselected,button,capsuleselected,verticalselected, andovalwithcolor="vibrant-dark"selected), Tab automatically inverts theiconimage to white viafilter: brightness(0) invert(1). If you're passing a pre-colored or already-white icon, use the#icon-{index}slot to render your own markup and bypass this filter.
Default selection: Tab does not auto-select the first tab on mount. The selected tab is whichever one has
value === modelValue; ifmodelValueis''(the default) or doesn't match anytab.value, no tab is highlighted. Initialize yourv-modelref to the desired tab'svalue(e.g.const selected = ref('tab1')) to pre-select on mount.
Badge clearing: badges are purely data-driven. Clicking a tab does not clear its red dot / content badge automatically — if you want "click to mark as read" behavior, mutate the
tabsarray (or theindie_tabobject) yourself in the@changehandler.
Attributes
| Attribute | Description | Type | Default |
|-----------|-------------|------|---------|
| v-model / modelValue | currently selected tab value. Must equal a tab.value for that tab to render as selected — no auto-selection on mount. Default '' means nothing is highlighted. | string \| number | '' |
| tabs | array of tab objects — each with value (required), label (required), icon? (string or false), badge? (true for dot, number for circle, string for pill, or object for full Badge props). Badges are data-driven: clicking a tab does not clear its badge; mutate the array yourself in @change if you want "click to mark as read". | Array | — (required) |
| type | tab type variant | 'outlined' \| 'filled' \| 'underline' \| 'button' \| 'vertical' \| 'capsule' \| 'oval' | 'underline' |
| color | color theme for capsule and oval types | 'sky' \| 'vibrant-dark' | 'sky' |
| width | total group width. Number → px, String accepts any CSS length ('50%', '32rem', '400px'). Omit to inherit parent (100%). | string \| number | '' |
| tab_width | fixed width applied to each tab. For horizontal types uses flex: 0 0 <w>; for vertical sets width / min-width directly (column flexbox would otherwise size height). Long labels ellipsis. Useful for grid-like layouts (e.g. EHR ez-bill main-tab 135px). | string \| number | '' |
| font_size | label font size. Number → px. When set, also relaxes line-height to 1.5 so labels don't clip when the font exceeds a per-type hard-coded line-height (e.g. underline's 20px). Leave empty to keep the variant's default line-height. | string \| number | '' |
| icon_size | icon box + SVG placeholder size. Number → px. Lets icons scale independently from font_size. | string \| number | '' |
| gap | gap between tabs inside the main group. Overrides per-variant default (filled 8px; outlined 0). | string \| number | '' |
| padding | horizontal padding on each tab (padding-inline). Vertical padding from CSS is preserved. | string \| number | '' |
| group_padding | horizontal padding on the group container itself (padding-inline). outlined and filled types add 0 24px by default; set this to 0 so tabs span the full configured width. Number → px; string accepts any CSS length. | string \| number | '' |
| equal_width | when true, the tab bar fills 100% of its container and tabs share that width equally (flex: 1 1 0; long labels ellipsis). For full-width header bars with no separate right-side tab. Ignored for vertical and when indie_tab is set. Overrides tab_width when both are passed. | boolean | false |
| overflow_mode | how to handle content exceeding the group width: 'scroll' (default, hidden horizontal scroll) or 'ellipsis' (tabs shrink in place, labels truncate). | 'scroll' \| 'ellipsis' | 'scroll' |
| indie_tab | separated independent tab: { value, label, icon?, badge? }. Positioning: underline → right-aligned (margin-left: auto); vertical → pinned to bottom (margin-top: auto, requires sized parent); all other types render immediately after the main group. | object | null |
Events
| Event | Description | Parameters |
|-------|-------------|------------|
| update:modelValue | syncs v-model when tab selection changes | (value: string \| number) |
| change | emitted when a different tab is selected | (value: string \| number) |
Slots
| Name | Description |
|------|-------------|
| #icon-{index} | Custom icon content for tab at given index. Receives { tab, selected } |
| #icon-indie | Custom icon for the indie tab |
Usage
Each of the 7 type variants below has two example sets: "Dot badge" (boolean badge: true) and "Badge with content" (number / string / object).
Outlined (Page Tab)
The icon field on each tab accepts an imported SVG/PNG path (or false to hide the icon). Tab automatically inverts dark-background icons to white where appropriate.
<template>
<!-- Dot badge -->
<Tab v-model="selected" type="outlined" :tabs="primaryTabs" :indie_tab="primaryIndie" />
<!-- Badge with content: dot, number, 99+, text pill -->
<Tab v-model="selected2" type="outlined" :tabs="badgeTabs" :indie_tab="badgeIndie" />
</template>
<script setup>
import { ref } from 'vue';
import { Tab } from '@vibrant-wellness/va-responsive-components-library';
// Icons — any SVG/PNG path; pass `false` on a tab to hide its icon
import iconVP from '@/assets/icons/vibrant-products.svg';
import iconSP from '@/assets/icons/signature-programs.svg';
import iconPP from '@/assets/icons/practice-products.svg';
import iconPB from '@/assets/icons/product-bundles.svg';
import iconCF from '@/assets/icons/customized-fee.svg';
import iconEZ from '@/assets/icons/ez-bill.svg';
import iconDT from '@/assets/icons/document-template.svg';
import iconAU from '@/assets/icons/automation.svg';
import iconTM from '@/assets/icons/template-marketplace.svg';
import iconAR from '@/assets/icons/archive.svg';
import iconMS from '@/assets/icons/messages.svg';
import iconIN from '@/assets/icons/inbox.svg';
const selected = ref('tab1');
const selected2 = ref('tab1');
const primaryTabs = [
{ value: 'tab1', label: 'Option 1', icon: iconVP, badge: true },
{ value: 'tab2', label: 'Option 2', icon: iconSP, badge: true },
{ value: 'tab3', label: 'Option 3', icon: iconPP, badge: true },
{ value: 'tab4', label: 'Option 4', icon: iconPB, badge: true },
{ value: 'tab5', label: 'Option 5', icon: iconCF, badge: true },
];
const primaryIndie = { value: 'indie', label: 'Option 6', icon: iconEZ, badge: true };
const badgeTabs = [
{ value: 'tab1', label: 'Messages', icon: iconMS, badge: true },
{ value: 'tab2', label: 'Inbox', icon: iconIN, badge: 3 },
{ value: 'tab3', label: 'Alerts', icon: iconAU, badge: 12 },
{ value: 'tab4', label: 'Pending', icon: iconTM, badge: 100 },
{ value: 'tab5', label: 'Spam', icon: iconCF, badge: 'New' },
];
const badgeIndie = { value: 'tab6', label: 'Archive', icon: iconAR };
</script>The
Filled,Underline,Button,Vertical,Capsule, andOvalexamples below are self-contained — each one imports only the icons it uses and defines its owntabs/indie_tab. Only thetype(and optionallycolor, sizing props) changes per variant.
Filled (Large Page Tab)
Selected tab uses a dark-blue fill; icon images are auto-inverted to white on the selected tab.
<template>
<!-- Dot badge -->
<Tab v-model="selected" type="filled" :tabs="primaryTabs" :indie_tab="primaryIndie" />
<!-- Badge with content (selected tab renders badge as white with dark text) -->
<Tab v-model="selected2" type="filled" :tabs="badgeTabs" :indie_tab="badgeIndie" />
</template>
<script setup>
import { ref } from 'vue';
import { Tab } from '@vibrant-wellness/va-responsive-components-library';
import iconVP from '@/assets/icons/vibrant-products.svg';
import iconSP from '@/assets/icons/signature-programs.svg';
import iconPP from '@/assets/icons/practice-products.svg';
import iconPB from '@/assets/icons/product-bundles.svg';
import iconCF from '@/assets/icons/customized-fee.svg';
import iconEZ from '@/assets/icons/ez-bill.svg';
import iconDT from '@/assets/icons/document-template.svg';
import iconAU from '@/assets/icons/automation.svg';
import iconTM from '@/assets/icons/template-marketplace.svg';
import iconAR from '@/assets/icons/archive.svg';
import iconMS from '@/assets/icons/messages.svg';
import iconIN from '@/assets/icons/inbox.svg';
const selected = ref('tab1');
const selected2 = ref('tab1');
const primaryTabs = [
{ value: 'tab1', label: 'Option 1', icon: iconVP, badge: true },
{ value: 'tab2', label: 'Option 2', icon: iconSP, badge: true },
{ value: 'tab3', label: 'Option 3', icon: iconPP, badge: true },
{ value: 'tab4', label: 'Option 4', icon: iconPB, badge: true },
{ value: 'tab5', label: 'Option 5', icon: iconCF, badge: true },
];
const primaryIndie = { value: 'indie', label: 'Option 6', icon: iconEZ, badge: true };
const badgeTabs = [
{ value: 'tab1', label: 'Messages', icon: iconMS, badge: true },
{ value: 'tab2', label: 'Inbox', icon: iconIN, badge: 3 },
{ value: 'tab3', label: 'Alerts', icon: iconAU, badge: 12 },
{ value: 'tab4', label: 'Pending', icon: iconTM, badge: 100 },
{ value: 'tab5', label: 'Spam', icon: iconCF, badge: 'New' },
];
const badgeIndie = { value: 'tab6', label: 'Archive', icon: iconAR };
</script>Underline (Medium Section Tab)
<template>
<!-- Dot badge -->
<Tab v-model="selected" type="underline" :tabs="primaryTabs" :indie_tab="primaryIndie" />
<!-- Badge with content -->
<Tab v-model="selected2" type="underline" :tabs="badgeTabs" :indie_tab="badgeIndie" />
</template>
<script setup>
import { ref } from 'vue';
import { Tab } from '@vibrant-wellness/va-responsive-components-library';
import iconVP from '@/assets/icons/vibrant-products.svg';
import iconSP from '@/assets/icons/signature-programs.svg';
import iconPP from '@/assets/icons/practice-products.svg';
import iconPB from '@/assets/icons/product-bundles.svg';
import iconCF from '@/assets/icons/customized-fee.svg';
import iconEZ from '@/assets/icons/ez-bill.svg';
import iconDT from '@/assets/icons/document-template.svg';
import iconAU from '@/assets/icons/automation.svg';
import iconTM from '@/assets/icons/template-marketplace.svg';
import iconAR from '@/assets/icons/archive.svg';
const selected = ref('tab1');
const selected2 = ref('tab1');
const primaryTabs = [
{ value: 'tab1', label: 'Option 1', icon: iconVP },
{ value: 'tab2', label: 'Option 2', icon: iconSP },
{ value: 'tab3', label: 'Option 3', icon: iconPP },
{ value: 'tab4', label: 'Option 4', icon: iconPB, badge: true },
{ value: 'tab5', label: 'Option 5', icon: iconCF, badge: true },
];
const primaryIndie = { value: 'indie', label: 'Option 6', icon: iconEZ, badge: true };
const badgeTabs = [
{ value: 'tab1', label: 'Messages', icon: iconEZ, badge: true },
{ value: 'tab2', label: 'Inbox', icon: iconDT, badge: 3 },
{ value: 'tab3', label: 'Alerts', icon: iconAU, badge: 12 },
{ value: 'tab4', label: 'Pending', icon: iconTM, badge: 100 },
{ value: 'tab5', label: 'Spam', icon: iconAR, badge: 'New' },
];
const badgeIndie = { value: 'tab6', label: 'Archive', icon: iconVP };
</script>Button Tab
Full-width dark-blue button row; all tabs have a dark background, so icon images are always inverted to white. indie_tab is not typically used with this type.
<template>
<!-- Dot badge -->
<Tab v-model="selected" type="button" :tabs="primaryTabs" />
<!-- Badge with content -->
<Tab v-model="selected2" type="button" :tabs="badgeTabs" />
</template>
<script setup>
import { ref } from 'vue';
import { Tab } from '@vibrant-wellness/va-responsive-components-library';
import iconVP from '@/assets/icons/vibrant-products.svg';
import iconSP from '@/assets/icons/signature-programs.svg';
import iconPP from '@/assets/icons/practice-products.svg';
import iconPB from '@/assets/icons/product-bundles.svg';
import iconCF from '@/assets/icons/customized-fee.svg';
import iconEZ from '@/assets/icons/ez-bill.svg';
import iconDT from '@/assets/icons/document-template.svg';
import iconAU from '@/assets/icons/automation.svg';
import iconTM from '@/assets/icons/template-marketplace.svg';
import iconAR from '@/assets/icons/archive.svg';
const selected = ref('tab1');
const selected2 = ref('tab1');
const primaryTabs = [
{ value: 'tab1', label: 'Option 1', icon: iconVP, badge: true },
{ value: 'tab2', label: 'Option 2', icon: iconSP, badge: true },
{ value: 'tab3', label: 'Option 3', icon: iconPP, badge: true },
{ value: 'tab4', label: 'Option 4', icon: iconPB, badge: true },
{ value: 'tab5', label: 'Option 5', icon: iconCF, badge: true },
];
const badgeTabs = [
{ value: 'tab1', label: 'Messages', icon: iconEZ, badge: true },
{ value: 'tab2', label: 'Inbox', icon: iconDT, badge: 3 },
{ value: 'tab3', label: 'Alerts', icon: iconAU, badge: 12 },
{ value: 'tab4', label: 'Pending', icon: iconTM, badge: 100 },
{ value: 'tab5', label: 'Spam', icon: iconAR, badge: 'New' },
];
</script>Vertical Menu Tab
Requires a sized parent container (the component fills 100% height). indie_tab is pinned to the bottom via margin-top: auto.
<template>
<!-- Dot badge -->
<div style="width: 320px; height: 900px; border: 1px solid #e5e7eb; border-radius: 8px; background: #fff;">
<Tab v-model="selected" type="vertical" :tabs="navTabs" :indie_tab="navIndie" />
</div>
<!-- Badge with content -->
<div style="width: 320px; height: 900px; border: 1px solid #e5e7eb; border-radius: 8px; background: #fff;">
<Tab v-model="selected2" type="vertical" :tabs="navBadgeTabs" :indie_tab="navIndie" />
</div>
</template>
<script setup>
import { ref } from 'vue';
import { Tab } from '@vibrant-wellness/va-responsive-components-library';
import iconEZ from '@/assets/icons/ez-bill.svg';
import iconDT from '@/assets/icons/document-template.svg';
import iconAU from '@/assets/icons/automation.svg';
import iconTM from '@/assets/icons/template-marketplace.svg';
import iconAR from '@/assets/icons/archive.svg';
import iconCF from '@/assets/icons/customized-fee.svg';
const selected = ref('ez-bill');
const selected2 = ref('ez-bill');
const navTabs = [
{ value: 'ez-bill', label: 'eZ-Bill', icon: iconEZ, badge: true },
{ value: 'document-template', label: 'Document Template', icon: iconDT, badge: true },
{ value: 'automation', label: 'Automation', icon: iconAU },
{ value: 'template-marketplace', label: 'Template Marketplace', icon: iconTM },
{ value: 'archive', label: 'Archive', icon: iconAR, badge: true },
];
const navBadgeTabs = [
{ value: 'ez-bill', label: 'eZ-Bill', icon: iconEZ, badge: true },
{ value: 'document-template', label: 'Document Template', icon: iconDT, badge: 5 },
{ value: 'automation', label: 'Automation', icon: iconAU, badge: 12 },
{ value: 'template-marketplace', label: 'Template Marketplace', icon: iconTM, badge: 100 },
{ value: 'archive', label: 'Archive', icon: iconAR, badge: 'New' },
];
const navIndie = { value: 'customized-fee', label: 'Customized Fee', icon: iconCF };
</script>Capsule Switch Tab (two color variants: sky default, vibrant-dark)
<template>
<!-- Sky (default) — dot badge -->
<div style="width: 540px;">
<Tab v-model="selected" type="capsule" :tabs="switchTabs" />
</div>
<!-- Sky — badge with content -->
<div style="width: 540px;">
<Tab v-model="selected2" type="capsule" :tabs="switchBadgeTabs" />
</div>
<!-- Vibrant Dark — dot badge -->
<div style="width: 540px;">
<Tab v-model="selected3" type="capsule" color="vibrant-dark" :tabs="switchTabs" />
</div>
<!-- Vibrant Dark — badge with content -->
<div style="width: 540px;">
<Tab v-model="selected4" type="capsule" color="vibrant-dark" :tabs="switchBadgeTabs" />
</div>
</template>
<script setup>
import { ref } from 'vue';
import { Tab } from '@vibrant-wellness/va-responsive-components-library';
import iconVP from '@/assets/icons/vibrant-products.svg';
import iconPP from '@/assets/icons/practice-products.svg';
const selected = ref('vibrant');
const selected2 = ref('vibrant');
const selected3 = ref('vibrant');
const selected4 = ref('vibrant');
const switchTabs = [
{ value: 'vibrant', label: 'Vibrant Products', icon: iconVP, badge: true },
{ value: 'practice', label: 'Practice Products', icon: iconPP, badge: true },
];
const switchBadgeTabs = [
{ value: 'vibrant', label: 'Vibrant Products', icon: iconVP, badge: 3 },
{ value: 'practice', label: 'Practice Products', icon: iconPP, badge: 12 },
];
</script>Oval Switch Tab (two color variants: sky default, vibrant-dark)
<template>
<!-- Sky (default) — dot badge -->
<div style="width: 580px;">
<Tab v-model="selected" type="oval" :tabs="switchTabs" />
</div>
<!-- Sky — badge with content -->
<div style="width: 580px;">
<Tab v-model="selected2" type="oval" :tabs="switchBadgeTabs" />
</div>
<!-- Vibrant Dark — dot badge -->
<div style="width: 580px;">
<Tab v-model="selected3" type="oval" color="vibrant-dark" :tabs="switchTabs" />
</div>
<!-- Vibrant Dark — badge with content -->
<div style="width: 580px;">
<Tab v-model="selected4" type="oval" color="vibrant-dark" :tabs="switchBadgeTabs" />
</div>
</template>
<script setup>
import { ref } from 'vue';
import { Tab } from '@vibrant-wellness/va-responsive-components-library';
import iconVP from '@/assets/icons/vibrant-products.svg';
import iconPP from '@/assets/icons/practice-products.svg';
const selected = ref('vibrant');
const selected2 = ref('vibrant');
const selected3 = ref('vibrant');
const selected4 = ref('vibrant');
const switchTabs = [
{ value: 'vibrant', label: 'Vibrant Products', icon: iconVP, badge: true },
{ value: 'practice', label: 'Practice Products', icon: iconPP, badge: true },
];
const switchBadgeTabs = [
{ value: 'vibrant', label: 'Vibrant Products', icon: iconVP, badge: 3 },
{ value: 'practice', label: 'Practice Products', icon: iconPP, badge: 12 },
];
</script>Click-to-clear-badge pattern (badges are data-driven, so you clear them yourself in @change)
<template>
<Tab v-model="selected" type="underline" :tabs="tabs" @change="onChange" />
</template>
<script setup>
import { ref } from 'vue';
import { Tab } from '@vibrant-wellness/va-responsive-components-library';
const selected = ref('tab1');
const tabs = ref([
{ value: 'tab1', label: 'Messages', badge: true },
{ value: 'tab2', label: 'Inbox', badge: 3 },
{ value: 'tab3', label: 'Alerts', badge: 12 },
]);
function onChange(value) {
const t = tabs.value.find(x => x.value === value);
if (t) t.badge = false; // mark-as-read on click
}
</script>Responsive Sizing
Tab adapts to any container by default (inherits parent width, scrolls horizontally when content overflows). For finer control, combine the responsive props below.
1. Total group width — width
<!-- Tab fills 380px regardless of parent. Number → px, or any CSS length string. -->
<Tab v-model="selected" type="filled" :width="380" :tabs="tabs" />
<Tab v-model="selected" type="oval" width="50%" :tabs="tabs" />
<Tab v-model="selected" type="outlined" width="32rem" :tabs="tabs" />2. Fixed per-tab width + label font — tab_width + font_size (e.g. EHR ez-bill main-tab style)
<Tab
v-model="selected"
type="filled"
:tab_width="135"
:font_size="12"
:tabs="tabs"
/>3. Density presets — gap + padding + font_size + icon_size
<!-- Compact (sidebar / dense list) -->
<Tab
v-model="selected"
type="underline"
:gap="2"
:padding="6"
:font_size="12"
:icon_size="14"
:tabs="tabs"
/>
<!-- Spacious (page header) -->
<Tab
v-model="selected"
type="underline"
:gap="24"
:padding="20"
:font_size="16"
:icon_size="24"
:tabs="tabs"
/>4. Overflow strategy — overflow_mode
<!-- Default: horizontal scroll (scrollbar hidden) — labels stay readable -->
<Tab v-model="selected" type="filled" :width="380" overflow_mode="scroll" :tabs="tabs" />
<!-- Or: ellipsis — tabs shrink in place, labels truncate where they sit -->
<Tab v-model="selected" type="filled" :width="380" overflow_mode="ellipsis" :tabs="tabs" />All responsive props are independent — combine any of them.
capsulealways shares parent width evenly across its tabs because that is its design semantic.
ResponsiveButton
Responsive button component with multiple button types (filled, outlined, text), built-in themes (light, dark, error), and flexible sizing. Supports prefix/suffix slots, custom styling, and interaction effects.
Attributes
| Attribute | Description | Type | Default |
|-----------|-------------|------|---------|
| display_name | button text content | String | '' |
| size | button size variant | 'small' \| 'medium' \| 'large' | 'small' |
| width_type | button width behavior | 'fill-whole' \| 'fit-content' | 'fit-content' |
| button_type | visual style type | 'filled' \| 'outlined' | 'filled' |
| built_in_theme | color theme | 'light' \| 'dark' \| 'error' | 'dark' |
| button_border_radius | CSS border-radius value | String | '4px' |
| customized_class | additional CSS class(es) | String | '' |
| button_id | identifier for click event payload | String | '' |
| disabled | disable button interaction and click emission | Boolean | false |
| is_active | when false, renders with disabled-like styling (grey) but the button remains fully clickable and emits click-button normally | Boolean | true |
Events
| Event | Description | Parameters |
|-------|-------------|------------|
| click-button | triggers when button is clicked | (buttonId: String, displayName: String) |
Slots
| Name | Description |
|------|-------------|
| default | button text content (alternative to display_name) |
| prefix | content before button text |
| suffix | content after button text |
<template>
<ResponsiveButton
display_name="Save Changes"
size="medium"
button_type="filled"
built_in_theme="dark"
width_type="fit-content"
@click-button="handleSave"
>
<template #prefix>
<svg viewBox="0 0 16 16"><path d="M8 2L6 6H2L5 9L4 14L8 11L12 14L11 9L14 6H10L8 2Z"/></svg>
</template>
</ResponsiveButton>
</template>
<script setup>
function handleSave(buttonId, displayName) {
console.log('Button clicked:', buttonId, displayName)
}
</script>Theme/Behavior Notes
Button Types:
- filled: Solid background with theme color, white text
- outlined: Transparent background with colored border, theme-colored text
- text: No background or border, blue text by default. The
errortheme applies red color with appropriate hover/active states.lightanddarkthemes have no visual effect.
Built-in Theme Colors:
- dark:
var(--VibrantDarkBlue)- primary dark blue - light:
var(--New_Button_SkyBlue)- sky blue - error:
var(--ErrorRed)- error red
Interaction States:
- Hover: Filled buttons change to green hover color; outlined buttons darken; text buttons darken (or use error red hover if
errortheme) - Active: Filled buttons change to press blue color; outlined buttons show pressed state; text buttons show pressed background (or use error red pressed if
errortheme) - Disabled (
disabled="true"): Gray background/border with gray text,not-allowedcursor, click events are not emitted - Inactive (
is_active="false"): Same grey visual as disabled, but cursor stays normal, the element remains clickable, andclick-buttonis still emitted — use when a button should look deselected/inactive but still respond to interaction
Size Variants:
- small:
4px 24pxpadding,4pxgap - medium:
8px 24pxpadding,6pxgap - large:
12px 32pxpadding,8pxgap - text buttons use
8pxhorizontal padding regardless of size
Deprecated Components
InputBox
Versatile input field with clearable icon, inside/outside label, styled themes, and validation helpers. The component dynamically adjusts border/background/label colors based on state (hover, focus, disabled, alert, error) and supports prefix/suffix slots.
Attributes
| Attribute | Description | Type | Default |
|-----------|-------------|------|---------|
| v-model / modelValue | bound input value | string | '' |
| label | label text (optional) | string | '' |
| label_type | where to render the label | 'inside' \| 'outside' | 'outside' |
| theme_type | visual styling theme | 'filled' \| 'outlined' | 'outlined' |
| customized_theme_color | focus/brand color used for caret color and focus border or underline; any valid CSS color or CSS var | string | '' |
| type | native input type | string | 'text' |
| placeholder | placeholder text | string | 'Enter' |
| clearable | show a clear icon when focused/hovered and value is non-empty | boolean | false |
| required | show a required asterisk on the label. Visual-only; does not enforce validation | boolean | false |
| disabled | disable the input | boolean | false |
| autocomplete | native autocomplete | string | 'off' |
Behavior notes:
- In
filledtheme, a bottom inset shadow emulates the underline. Inoutlinedtheme, an inset border is used. Colors are derived from:- Focus:
customized_theme_color(orvar(--Schemes-primary)fallback) - Hover:
#17181C - Disabled:
var(--disabled-color--)andvar(--disabled-button-background-color--) - Alert/Error:
var(--semantic-alert-color--)/var(--semantic-error-color--)
- Focus:
- Inside label floats above the input content area and inherits a state color similar to the border color.
- A message region appears below the field when either
#supportingTextslot has content or when alert/error is active with a message. #supportingTextis neutral and does not adopt alert/error colors; alert/error messages are colored separately.- Alert/Error messages are set via exposes:
alert(message)/error(message); callremoveAlertOrErrorEffect()to clear. - Disabled state down-weights message colors.
Events
| Event | Description | Parameters |
|-------|-------------|------------|
| input | fires on each keystroke | (value: string) |
| change | fires on Enter or blur | (value: string) |
| focus | input focused | () |
| blur | input blurred | () |
| clear | clear button clicked | () |
Exposes
| Method | Description | |--------|-------------| | focus | programmatically focus the input | | blur | programmatically blur the input | | alert | show alert state with a message (yellow) | | error | show error state with a message (red) | | removeAlertOrErrorEffect | clear alert/error state |
Slots
| Name | Description |
|------|-------------|
| prefix | content rendered before the input (e.g., an icon) |
| suffix | content rendered after the input (e.g., an icon) |
| supportingText | optional helper text rendered below the field (e.g., character counter like 0/100). This is distinct from alert/error messages and uses a neutral color. When alert/error is active, their messages render alongside (with their own colors) |
Tip: For prefix/suffix icons, prefer inline SVG that uses
fill="currentColor". This way the icon color automatically follows the component state (hover/focus/disabled/alert/error). If you use<img>sources, they won't inherit color.
Usage
<template>
<InputBox
v-model="value"
label="Email"
label_type="inside"
theme_type="outlined"
customized_theme_color="var(--Schemes-primary)"
type="email"
placeholder="[email protected]"
clearable
autocomplete="email"
>
<template #prefix>
<img src="/icons/mail.svg" style="width:100%;height:100%"/>
</template>
</InputBox>
</template>
<script setup>
import { ref } from 'vue'
const value = ref('')
</script>
#### SingleSelector
Rebranded underline-only dropdown (Figma "Dropdownbox_item_new"). The label is rendered by the shared `Label` component (GT Pressura Mono, uppercase) above or beside the trigger; the trigger is a 1px Black_W_70 underline that thickens to 2px Black on hover/open, with a 24px arrow chevron that flips when open. The menu is a opaque light-grey card (#F7F7F7, 10px radius, white hairline border, soft shadow, ABC Oracle Book section headers) 4px below the trigger with 46/38/30px rows. Supports filtering, optional remote search with pagination, grouped sections, a fully customizable per-option and section-header layout, prefix/suffix slots, and the shared help / error / alert message rows (`InputMessage`) below the field. `Esc` closes the open menu. Object / array option values are matched by content, so a value that arrives as an equal copy still highlights its row.
Sizes: `large` → 42px trigger / 16px text / 14px label; `medium` → 36px / 14px / 12px; `small` → 30px / 14px / 12px. The retired `x-small` is accepted as an alias of `small`.
##### Attributes
| Attribute | Description | Type | Default |
|-----------|-------------|------|---------|
| v-model / modelValue | binding value (selected option's `value`) | `string \| number \| boolean \| object \| array` | `''` |
| label | label text rendered by the `Label` component (pressura font); empty = no label row | `string` | `''` |
| label_type | **Deprecated.** The label is always rendered above/beside the trigger; `'inside'` is accepted but renders the same | `'inside' \| 'outside'` | `'outside'` |
| layout | `'vertical'` (label above) or `'horizontal'` (label left of the trigger) — forwarded to `Label` | `'vertical' \| 'horizontal'` | `'vertical'` |
| label_width | horizontal layout only: fixed width of the label column (number = px) — forwarded to `Label` | `number \| string` | `null` |
| required | show the required marker next to the label | `boolean` | `false` |
| required_text | the required marker text | `string` | `'*'` |
| size | trigger size (see Sizes above); legacy `x-small` renders as `small` | `'large' \| 'medium' \| 'small'` | `'x-small'` (→ small) |
| theme_type | **Deprecated.** The trigger is underline-only; `'filled'` renders the same as `'outlined'` | `'filled' \| 'outlined'` | `'outlined'` |
| customized_theme_color | caret color and focus/brand color; any CSS color or CSS var | `string` | `''` |
| error_wording | error message with the error icon, stacked beneath any help message; puts the selector in the error state | `string` | `''` |
| error_border_color | overrides the error accent (underline, label, chevron, message) while in the error state; setting it alone also enables the error state. Ignored while disabled | `string` | `''` |
| helper_action_text | optional action link after the help message (emits `helper-action-click`) | `string` | `''` |
| error_action_text | optional action link after the error / alert message (emits `error-action-click`) | `string` | `''` |
| options | options array — each option supports `value`, `label`, `prefix?`, `suffix?`, `prefix_slot_raw_html_content?`, `suffix_slot_raw_html_content?`, `disabled?`, `is_selected?` | `Array<Option>` | `() => []` |
| filterable | enable client-side filter input | `boolean` | `false` |
| remote_search | emit query outward instead of local filtering | `boolean` | `false` |
| remote_search_pagination | (with `remote_search`) enable infinite-scroll pagination — emits `remote-search-load-more` | `boolean` | `false` |
| remote_search_has_more | parent-controlled flag: whether more pages are available | `boolean` | `false` |
| remote_search_load_more_loading | parent-controlled flag: whether the next page is being fetched; shows a "Loading more..." row | `boolean` | `false` |
| remote_search_scroll_threshold | px from bottom of the scroll area that triggers `remote-search-load-more` | `number` | `80` |
| disabled | disable selector | `boolean` | `false` |
| placeholder | placeholder when no value | `string` | `'Select'` |
| clearable | show the "Clear" text button (same `TextButton` as `TextInput`'s) when there is a value and focused/hovered | `boolean` | `false` |
| dropdown_max_height | max height of the dropdown scroll area (any CSS height) | `string` | `'400px'` |
| dropdown_width | override the dropdown width (any CSS width, e.g. `'300px'`); empty = match the trigger width | `string` | `''` |
| loading | loading state (mirrors dropdown's `is_loading`) | `boolean` | `false` |
| loading_text | loading text | `string` | `'Loading'` |
| no_data_text | empty-state text | `string` | `'No data'` |
| filter_only_among_options_value | filter against serialized `value` only | `boolean` | `false` |
| filter_only_among_options_label | filter against `label` only | `boolean` | `false` |
| helper_info | help message (info icon) displayed below the field | `string` | `''` |
| background_color_override | override the trigger's background color (ignored when `disabled`); the rebranded trigger is transparent by default | `string` | `''` |
| customizedOptionStyle | render each option via the `#option` scoped slot (slot props: `{ option, index, is_selected, disable