Newer
Older
gnexus-ui-kit / CLAUDE.md

GNexus UI Kit — AI Agent Reference

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.

Golden Rule

Prefer gnexus-ui-kit/vue components. Do not recreate modal, drawer, tab, toast, form, button, or table markup manually unless the adapter does not expose the needed component yet.

Demo Synchronization

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.

  • Section order, navigation items, and currentSection observer targets must match exactly.
  • If a Vue adapter component cannot render markup identical to the vanilla partial, use raw HTML from the vanilla partial in the Vue demo. Do not force the adapter to match at the cost of visual differences.
  • Verify synchronization after every edit by running the Playwright height comparison (node compare-heights.js) — it must report 0 differences across all sections.

Required Setup (once per app)

import "gnexus-ui-kit/dist/css/kit.css";
import "gnexus-ui-kit/dist/assets/fonts/phosphor-icons/src/css/icons.css";

Import Pattern

Named imports (preferred):

import { GnButton, GnInput, GnModal } from "gnexus-ui-kit/vue";

Global registration:

import { GnexusUiVue } from "gnexus-ui-kit/vue";
app.use(GnexusUiVue);

Icon Naming

All icons must carry the ph class. Every icon — everywhere, always. Phosphor Icons are the only supported icon set.

  • In Vue icon props: always use the ph- prefix (ph-house, ph-bold ph-spinner).
  • In raw HTML: always use <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>

Component Catalog

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
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

Variants

Use only these variant names:

primary, secondary, accent, success, warning, danger, error, info

danger and error render the same color in most components.

What NOT to do

  • Do not copy raw modal markup from demo partials into Vue apps.
  • Do not call GNexusUIKit.Modals.create() from Vue components.
  • Do not call GNexusUIKit.Overlays.init() or GNexusUIKit.NavigationShell.init() in Vue projects.
  • Do not run Accordion.init() or Tabs.init() inside Vue components.
  • Do not use the legacy advancedSelect() helper in Vue; use GnCombobox.
  • Do not use InputPatterns.init() for Vue file upload previews; use GnFileUpload.
  • Do not invent new variant names.
  • Do not duplicate GNexus CSS in scoped <style> blocks.
  • Do not write class="btn btn-primary" when GnButton exists.

Acceptable Raw Markup

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.

Slot Conventions

  • 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 })

Behavior Contracts

  • 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.

Working on This Repo — Lessons

These rules exist because previous sessions produced regressions when they were ignored.

One change, one verification

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.

Always compare with vanilla HTML before implementing or fixing a Vue component

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.

Screenshot every visual change

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."

CSS transitions + Vue render cycle

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.

Validate prop contracts against demo data

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.

Components with intrinsic max-width do not belong in tight grids

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.

Do not stack unrelated UI into one demo section

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.

Check event handler syntax in Vue templates

@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.

Verification

Before claiming Vue adapter compatibility in this repo, run:

npm run build
npm run test:vue-adapter