Appearance
Global Styles & Design Tokens
Your plugin's Vue components are loaded into the same page as the rest of EA, so they inherit all of the app's global CSS automatically — no imports needed. This page documents what's actually available: color/spacing tokens, ready-made classes for common page shapes, and a couple of conventions worth copying even though they aren't shipped as global classes yet.
Design Tokens (CSS Custom Properties)
EA's active PrimeVue theme defines these as CSS custom properties on :root. Use them in your own <style> blocks (or inline styles) instead of hardcoding colors, so your plugin stays correct if the user switches theme:
| Token | Default value | Use for |
|---|---|---|
--surface-ground | #eff3f8 | Page background |
--surface-card | #ffffff | Cards, panels, chrome |
--surface-hover | #f6f9fc | Row/item hover background |
--surface-border | #dfe7ef | Hairline borders |
--text-color | #495057 | Primary text |
--text-color-secondary | #6c757d | Secondary/muted text |
--primary-color | #6366f1 | The one accent color — don't introduce a second hue for emphasis |
--highlight-bg | theme-dependent | Active/selected row background |
--highlight-text-color | theme-dependent | Text on --highlight-bg |
vue
<style scoped>
.my-panel {
background: var(--surface-card);
border: 1px solid var(--surface-border);
color: var(--text-color);
}
.my-panel:hover {
background: var(--surface-hover);
}
</style>These are runtime CSS variables, safe to reference from any plugin component. Don't reach for SCSS
$variables(like$borderRadius) — those are compiled into the app's own bundle at build time and aren't available to a dynamically-loaded plugin SFC.
Icons & Utility Classes
Two icon sets and PrimeFlex's utility classes (flex, grid, col-*, gap-2, w-full, spacing/text-color helpers, etc.) are globally available — see the Icons and PrimeFlex Reference sections of the Vue UI Guide for the full tables.
Ready-to-Use Global Classes
These classes are defined in the app's own global stylesheet — reference them directly in your plugin templates without redefining anything.
Page header — .page-header, .page-header__left, .page-header__identity, .page-header__icon-badge, .page-header__title, .page-header__subtitle, .page-header__actions, .icon-only-btn, .back-btn
List/table pages — .table-card, .table-toolbar, .table-toolbar__left, .table-toolbar__right, .table-title, .search-wrap / .search-input, .toolbar-count, .clean-table (apply to <DataTable class="clean-table">), .row-actions, .action-btn (add --danger, --success, or --warning for hover-tinted variants), .empty-state, .empty-state__icon-badge, .empty-state__title, .empty-state__subtitle
Entrance animation — .page-enter (subtle fade-in-up on mount; add to your page's root element)
Example: a typical list page
vue
<template>
<div class="page-enter">
<div class="page-header">
<div class="page-header__left">
<div class="page-header__identity">
<div class="page-header__icon-badge"><i class="pi pi-music"></i></div>
<div>
<div class="page-header__title">Playlists</div>
<div class="page-header__subtitle">Manage your plugin's playlists</div>
</div>
</div>
</div>
<div class="page-header__actions">
<Button icon="pi pi-plus" label="New" @click="open('add')" />
</div>
</div>
<div class="table-card">
<div class="table-toolbar">
<div class="table-toolbar__left">
<span class="table-title">All Playlists</span>
<span class="toolbar-count">{{ items.length }}</span>
</div>
<div class="table-toolbar__right search-wrap">
<InputText v-model="q" class="search-input" placeholder="Search..." />
</div>
</div>
<DataTable v-if="items.length" :value="items" class="clean-table">
<Column field="name" header="Name" />
<Column header="Actions">
<template #body="{ data }">
<div class="row-actions">
<button class="action-btn" @click="open(data.id)"><i class="pi pi-pencil"></i></button>
<button class="action-btn action-btn--danger" @click="remove(data)"><i class="pi pi-trash"></i></button>
</div>
</template>
</Column>
</DataTable>
<div v-else class="empty-state">
<div class="empty-state__icon-badge"><i class="pi pi-inbox"></i></div>
<div class="empty-state__title">No playlists yet</div>
<div class="empty-state__subtitle">Create one to get started</div>
</div>
</div>
</div>
</template>Form Page Pattern (copy, don't assume it's global)
Unlike the classes above, .form-card, .identity-strip, .form-section, and .field labels are not yet shipped as reusable global CSS — today they exist only as scoped styles inside a couple of the app's own pages. Copy the pattern into your own component's <style scoped> block rather than relying on it being available:
vue
<style scoped>
.form-card { background: var(--surface-card); border: 1px solid var(--surface-border); border-radius: 12px; padding: 1.5rem; }
.form-section { margin-bottom: 1.5rem; }
.form-section__label { font-weight: 600; color: var(--text-color); margin-bottom: 0.75rem; display: block; }
.field { margin-bottom: 1rem; }
</style>Things to Avoid
To keep plugin UI visually consistent with the rest of EA:
- No gradients, glassmorphism, or glow/neon shadows
- No hover-lift transforms on cards or rows
- No pill-shaped buttons — match the app's existing border-radius scale
- No oversized border-radius on cards/panels
- No per-item rainbow colors —
--primary-coloris the one accent; use severity colors (success/warn/danger) sparingly and consistently - Use
:focus-visiblefor keyboard-focus styling, not bare:focus