Skip to main content

UI Primitives - Team Conventions

Last Updated: March 2026
Status: ✅ Canonical naming conventions locked and migrated


Overview

This admin interface contains 10 reusable UI primitives that standardize all Admin interface patterns across Pagify. All components follow canonical prop naming conventions with backwards-compatible legacy aliases for smooth migration.


Component Inventory

ComponentPurposeKey Props
UiButtonPolymorphic button/linktag, tone, size, radius, fullWidth
UiCardContent containertag, padding
UiInputPolymorphic form controltag, modelValue, type, rows
UiFieldForm field label wrapperlabel, for, labelTone
UiAlertStatus/feedback messagestone (danger/warning/success/info)
UiSwitchBoolean toggle controlmodelValue, trueLabel, falseLabel, disabled
UiStatusBadgeInline status indicatorstone (neutral/success/warning/danger)
UiPageHeaderPage title/subtitletitle, subtitle
UiTableShellCard-wrapped tabletableClass, headClass, bodyClass
UiCrudActionsEdit/Delete button groupeditLabel, deleteLabel

Canonical vs Legacy Props

All primitives support canonical prop names (recommended) with legacy aliases (still valid):

Canonical (Use This)Legacy Alias (Still Works)Purpose
tag="section"as="section"HTML element type
tone="danger"variant="danger"Semantic color/meaning
radius="lg"rounded="lg"Border radius
full-widthblockFull width display
padding="none"no-paddingPadding control
for="username"for-id="username"Label association
edit-label="Edit"edit-text="Edit"Action button labels
delete-label="Delete"delete-text="Delete"Action button labels
label-tone="muted"label-class="..."Label text color

Migration Examples

Before (Legacy Aliases)

<UiCard as="section" no-padding>
<UiAlert variant="error">Error message</UiAlert>
<UiButton as="a" variant="danger" rounded="lg" block>
Delete
</UiButton>
<UiCrudActions
:edit-text="t.edit"
:delete-text="t.delete"
@edit="handleEdit"
@delete="handleDelete"
/>
</UiCard>

After (Canonical Props)

<UiCard tag="section" padding="none">
<UiAlert tone="danger">Error message</UiAlert>
<UiButton tag="a" tone="danger" radius="lg" full-width>
Delete
</UiButton>
<UiCrudActions
:edit-label="t.edit"
:delete-label="t.delete"
@edit="handleEdit"
@delete="handleDelete"
/>
</UiCard>

Both versions work! Canonical props are clearer and self-documenting.


Convention Tokens

All valid prop values are defined in ui-conventions.js:

import { UI_TAGS, UI_TONES, UI_BUTTON_SIZES, UI_BUTTON_RADII } from './ui-conventions'

// Valid values:
UI_TAGS // ['div', 'section', 'article', 'button', 'a', ...]
UI_TONES // ['primary', 'neutral', 'danger', 'success', 'warning', 'info']
UI_BUTTON_SIZES // ['xs', 'sm', 'md', 'lg']
UI_BUTTON_RADII // ['full', 'lg']
UI_CARD_PADDING // ['default', 'none']
UI_BADGE_TONES // ['neutral', 'success', 'warning', 'danger', 'info']
UI_INPUT_TAGS // ['input', 'textarea', 'select']

Runtime validation prevents typos:

<!-- ✅ Valid - renders correctly -->
<UiButton tone="danger" />

<!-- ❌ Invalid - Vue warning: "tone must be one of [primary, neutral, danger, ...]" -->
<UiButton tone="error" />

Tone → Color Mapping

ToneColorsUsage
primaryPurple (#1e1b4b, #5b21b6)Default actions, brand elements
neutralGray/SlateSecondary actions, cancel
dangerRose (#be123c, #fda4af)Delete, errors, destructive actions
successEmerald (#059669, #10b981)Confirmations, success states
warningAmber (#d97706, #fbbf24)Warnings, caution
infoBlueInformational messages
outlineWhite bg + borderGhost/minimal buttons

Best Practices

✅ DO

  • Use canonical props in new code (tag, tone, radius, fullWidth)
  • Import tokens from ui-conventions.js for custom validators
  • Leverage oneOf() validator in new components
  • Let computed resolvers handle alias normalization internally

❌ DON'T

  • Don't mix canonical and alias for same prop (use one consistently)
  • Don't hardcode invalid tone/tag values (trust the validators!)
  • Don't create new aliases without updating ui-conventions.js

Adding New Primitives

If you create a new UI primitive:

  1. Define canonical props with validators:

    <script setup>
    import { UI_TONES, oneOf } from './ui-conventions'

    defineProps({
    tone: {
    type: String,
    default: 'primary',
    validator: oneOf(UI_TONES)
    }
    })
    </script>
  2. Add legacy alias support (if needed):

    const props = defineProps({
    tone: { type: String, default: 'primary', validator: oneOf(UI_TONES) },
    variant: { type: String, default: undefined } // Legacy alias
    })

    const resolvedTone = computed(() => props.tone ?? props.variant ?? 'primary')
  3. Update ui-conventions.js if introducing new token arrays

  4. Document in this README under Component Inventory


Migration Status

PageStatusNotes
Login✅ MigratedAll canonical props
Dashboard✅ MigratedAll canonical props
Settings✅ MigratedAll canonical props
Tokens✅ MigratedAll canonical props
Profile✅ MigratedAll canonical props
Modules✅ MigratedAll canonical props
Updater✅ MigratedAll canonical props
Audit✅ MigratedAll canonical props
Permissions✅ MigratedAll canonical props
Admins✅ MigratedAll canonical props
AdminGroups✅ MigratedAll canonical props

All admin pages now use canonical props exclusively. Legacy aliases remain supported for backwards compatibility in modules/extensions.

Plugins Manager UI

  • Location: Admin > Settings > Modules
  • Includes plugin lifecycle actions: install (Composer/ZIP), enable/disable, uninstall
  • Shows compatibility state and safe-mode disable marker
  • Surfaces extension point counts for: field types, blocks, dashboard widgets, automation actions, menu items

Testing

Build validation:

cd themes/admin/default
npm run build
# ✓ built in 1.09s

Runtime validation:

  • All prop validators active in development mode
  • Invalid prop values trigger Vue warnings in console

Questions?

  • See examples: Browse Pages/Admin/**/Index.vue for real-world usage
  • Check conventions: Review ui-conventions.js for token definitions
  • Inspect components: Read JSDoc comments in each primitive's <script setup>

Convention locked ✅ - Safe for team-wide adoption.