Frontend Design System
Pkgly's UI is a Vue 3 + Vuetify 3 application whose visual identity is driven by a single source of truth for color and a small set of shared UI primitives. This page documents both so contributors can change the look of the whole app from one place.
Brand palette
Colors are derived from the Pkgly logo (site/public/logo.svg) — an orange + teal origami mark.
| Token | Value | Role |
|---|---|---|
primary | #C95A00 (deep orange) | CTAs, links, active states. Tuned for WCAG AA on white. |
secondary | #145964 (deep teal) | Secondary surfaces. |
accent | #FFB70B (amber) | Sparing highlights. |
success | #43A047 | Active / positive status. |
warning | #F9A825 | Shifted away from primary orange to stay distinct. |
error | #E53935 | Errors. |
Where colors live (single source)
The canonical palette is site/src/theme/palette.ts. Three layers consume it:
site/src/plugins/vuetify.ts— Vuetify theme colors (importspalette).site/src/utils/themeTokens.ts— runtime CSS-var applier (derives frompalette).site/src/assets/styles/tokens.scss— static:rootCSS custom properties (mirror of the palette, resolves before the JS applier runs).
site/src/assets/styles/theme.scss is now a thin SCSS bridge — it maps legacy $primary / $accent SCSS names onto the canonical CSS custom properties. No hex values live there.
A guard test (site/src/__tests__/themePalette.spec.ts) asserts that tokens.scss stays in sync with palette.ts. To change a color, update palette.ts and tokens.scss — the test will fail until they agree.
Spacing, radius, typography, elevation, motion
All non-color tokens are CSS custom properties in tokens.scss under the --nr-* prefix (spacing on a 4px base, radii, font sizes/weights, shadows, z-index, transitions). Prefer these tokens over hardcoded values in components.
Shared UI primitives
Reusable components live in site/src/components/ui/ and site/src/components/layout/. Prefer these over duplicating markup.
| Component | Path | Purpose |
|---|---|---|
StatusChip | components/ui/StatusChip.vue | Private/Public access badges based on authentication and visibility, plus Active/Inactive badges. |
MonoValue | components/ui/MonoValue.vue | Truncated hash/digest display with copy-to-clipboard. |
TableSkeleton | components/ui/TableSkeleton.vue | Loading placeholder that mimics table rows. |
EmptyState | components/ui/EmptyState.vue | Icon + title + message + optional action for empty collections. |
Breadcrumbs | components/layout/Breadcrumbs.vue | Accessible breadcrumb nav (final segment is active). |
BrandMark | components/layout/BrandMark.vue | Logo + wordmark lockup; horizontal (app bar) or stacked (login). |
Conventions
- Components are token-driven (
var(--nr-*)) so they adapt to the palette automatically. - Each primitive has a co-located
__tests__/*.spec.ts(Vitest +@vue/test-utils). - When adding a new status/badge/tone, add a token to
tokens.scssrather than a hardcoded color.
Form and table behavior
- Disabled primary buttons with flat or elevated variants use a tonal fill and dark primary text. The global override targets Vuetify's
bg-primaryclass. - Password confirmation mismatches invalidate the submitted value while preserving both typed fields for correction. Installation stays disabled until the password is valid and both fields match.
- Casbin model and policy remain editable in the administrator's OAuth2 settings when OAuth2 is enabled.
- Package page-size selectors remain available when results fit on one page; only page navigation is hidden.