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.