When working on a Vue project that depends on gnexus-ui-kit, always use the official Vue adapter components instead of writing raw HTML/CSS markup. This document is the single source of truth for which components exist and when to use them.
Prefer
gnexus-ui-kit/vuecomponents. Do not recreate modal, drawer, tab, toast, form, button, or table markup manually unless the adapter does not expose the needed component yet.
demo/index.html (vanilla) and demo/vue.html (Vue) must always be pixel-identical. Any change to one requires an immediate matching change to the other.
currentSection observer targets must match exactly.node compare-heights.js) — it must report 0 differences across all sections.import "gnexus-ui-kit/dist/css/kit.css"; import "gnexus-ui-kit/dist/assets/fonts/phosphor-icons/src/css/icons.css";
Named imports (preferred):
import { GnButton, GnInput, GnModal } from "gnexus-ui-kit/vue";
Global registration:
import { GnexusUiVue } from "gnexus-ui-kit/vue";
app.use(GnexusUiVue);
All icons must carry the ph class. Every icon — everywhere, always. Phosphor Icons are the only supported icon set.
icon props: always use the ph- prefix (ph-house, ph-bold ph-spinner).<i class="ph ph-house"></i> (both ph and ph-*).// Correct icon="ph-house" icon="ph-plus" icon="ph-bold ph-spinner" // Wrong — will trigger a dev warning and may not render icon="house" icon="plus"
<!-- Correct --> <i class="ph ph-house"></i> <!-- Wrong — missing base `ph` class --> <i class="ph-house"></i>
| Need | Component | Props you will use |
|---|---|---|
| Button | GnButton |
variant, size, icon, loading, disabled |
| Icon-only button | GnIconButton |
icon, label, size |
| Copy button | GnCopyButton |
text, icon, successIcon, duration, label, size |
| Status label | GnBadge |
variant, outline |
| Message block | GnAlert |
variant |
| Card / panel | GnCard |
title (slot), footer (slot) |
| Horizontal card | GnHorizontalCard |
image, title, titleHref, icon, footer (slot) |
| Login card | GnLoginCard |
title, logoIcon, usernameLabel, passwordLabel, rememberMe, submitText, error, forgotHref, signupHref |
| User card | GnUserCard |
name, email, role, avatar, href, compact, actions |
| Page title bar | GnPageHeader |
kicker, title, subtitle, meta, actions (slot) |
| Text field | GnInput |
v-model, label, icon, state, help |
| Textarea | GnTextarea |
v-model, label, state, help |
| Select dropdown | GnSelect |
v-model, label, options |
| Checkbox | GnCheckbox |
v-model, label, disabled |
| Toggle switch | GnSwitch |
v-model, label, disabled |
| Radio | GnRadio / GnRadioGroup |
v-model, options |
| Range slider | GnRange |
v-model, label, min, max |
| File upload | GnFileUpload |
v-model, badge, multiple, accept |
| Searchable select | GnCombobox |
v-model, label, options, placeholder |
| Tag / multi-value input | GnTagInput |
v-model, label, placeholder, separator, unique, maxItems |
| Tabs | GnTabs |
v-model, items |
| Router tabs | GnRouterTabs |
items (with to), activeMatch |
| Accordion | GnAccordion |
items, v-model, multiple |
| Modal dialog | GnModal |
v-model:open, title, closeOnBackdrop |
| Side drawer | GnDrawer |
v-model:open, title, position |
| Toasts | GnToastProvider + useToast |
Wrap app once; call toast.success({ title, text }) |
| Confirm dialog | GnConfirmDialog |
v-model:open, title, message, confirmVariant |
| Table | GnTable |
columns, rows, caption, emptyText |
| Toolbar | GnToolbar |
title, meta, actions (slot) |
| Input group | GnInputGroup |
addon (slot), action (slot) |
| Search field | GnSearchField |
v-model |
| Pagination | GnPagination |
page, total-pages |
| Empty state | GnEmptyState |
title, text, icon, actions (slot) |
| Skeleton loader | GnSkeleton |
— |
| Key-value list | GnDescriptionList |
items |
| Progress bar | GnProgress |
value, label, animated |
| Usage meter card | GnUsageMeter |
value, label, max |
| Staged progress | GnProgressStages |
items |
| Wizard steps | GnSteps |
items, current |
| Chip / tag | GnChip |
variant, selected, removable |
| Chip group | GnChipGroup |
— |
| Avatar | GnAvatar |
initials, icon, image, size, status |
| Identity row | GnIdentity |
title, meta, avatar props |
| Avatar stack | GnAvatarStack |
items |
| Timeline | GnTimeline |
items |
| Activity log | GnActivityLog |
items |
| Basic list | GnList |
items |
| Definition list | GnDefinitionList |
items |
| Action list | GnActionList |
items |
| Loader / spinner | GnLoader |
— |
| Status card | GnStatusCard |
variant, title, meta |
| Metric card | GnMetricCard |
title, value, trend |
| Action card | GnActionCard |
title, text, actions (slot) |
| Dropdown menu | GnDropdown |
items, label, variant |
| Popover panel | GnPopover |
title, text, label |
| Tooltip | GnTooltip |
text |
| Navigation list | GnNavList |
items |
| App shell | GnNavigationShell |
brand, items, current, footerLeft, footerRight |
Use only these variant names:
primary, secondary, accent, success, warning, danger, error, info
danger and error render the same color in most components.
GNexusUIKit.Modals.create() from Vue components.GNexusUIKit.Overlays.init() or GNexusUIKit.NavigationShell.init() in Vue projects.Accordion.init() or Tabs.init() inside Vue components.advancedSelect() helper in Vue; use GnCombobox.InputPatterns.init() for Vue file upload previews; use GnFileUpload.<style> blocks.class="btn btn-primary" when GnButton exists.Raw classes are only acceptable for layout wrappers and content that has no Vue adapter yet:
<div class="form-grid">...</div> <div class="demo-actions">...</div>
For any interactive component, add or extend the adapter first.
GnTabs uses tab ids as slot names: <template #overview>...</template>GnTable uses cell-${column.key}: <template #cell-status="{ value }"><GnBadge>{{ value }}</GnBadge></template>GnModal uses default, title, footer, and actions (actions receives { close })GnModal and GnDrawer handle Escape, focus return, and Tab trapping automatically.GnToastProvider shows one toast at a time: a new toast replaces the previous one.GnCombobox owns combobox/listbox ARIA and keyboard navigation.GnFileUpload owns preview object URLs and cleans them up on remove/unmount.These rules exist because previous sessions produced regressions when they were ignored.
Never make three edits and then check the result. After every single change, run npx gulp build, open the browser, and confirm the affected component still renders correctly. Catching a bug in the first edit saves more time than catching three stacked bugs in the fourth.
When adding or fixing a Vue adapter component (e.g., GnLoginCard), open demo/partials/*.html first. The vanilla HTML is the source of truth for markup structure, CSS classes, icon names, and default prop values. Do not trust the existing Vue component or the existing demo page to be correct.
If a component's appearance changed, take a Playwright screenshot before and after. A diff that looks "slightly off" often hides a real bug (wrong padding, missing border, clipped text). Do not dismiss visual glitches as "probably fine."
When a component's visibility is toggled via v-if / conditional rendering, the initial render must include the "hidden" state class first, and the "visible" state class must be applied in a nextTick + requestAnimationFrame. If both states exist in the same render frame, the browser skips the transition entirely. This applies to GnModal, GnDrawer, and any future overlay component.
If a demo passes { label: "Foo" } and the component expects { title: "Foo" }, the UI will render empty items. Before wiring demo data into a component, read the component's props declaration. If they don't match, fix the component fallback (e.g., item.title || item.label) or fix the demo data — don't leave it broken.
GnNavList, GnSteps, GnProgress, GnTimeline, GnHorizontalCard, GnLoginCard, GnUsageMeter, and GnUserCard define their own max-width in CSS. Putting them inside display: grid with minmax(320px, 1fr) causes overflow, clipping, or forced squishing. Render them as block-level siblings instead.
A section titled "Chips & Toolbar" that also contains Pagination creates confusion about component boundaries. Each major component gets its own demo-section with its own h3.
@click="toastRef.value?.success(...)" does not work in Vue 3 SFC/inline templates the way it works in plain JS. The template compiler handles ref unwrapping differently from setup() return values. If a method call fails silently, extract a helper function in setup() and call that instead.
Before claiming Vue adapter compatibility in this repo, run:
npm run build npm run test:vue-adapter