Skip to main content

Mall-OS UI (@mall-os/ui)

The dashboard and many tools are built on the common package @mall-os/ui (Tailwind v4, Radix-Primitives, shadcn-like components). Styling and behavior should be adjusted centrally, not duplicated per page.

Theme & Colors

  • Single Source of Truth: packages/ui/src/cockpit-dashboard-theme.css — all design tokens as HSL components (--background: 216 20% 97% …), ensuring the dashboard (Tailwind v3) and @mall-os/ui (Tailwind v4) utilize the same values.
  • Dashboard: imports the file in apps/dashboard/src/app/globals.css via @import "@mall-os/ui/cockpit-dashboard-theme.css".
  • UI Package: packages/ui/src/styles.css imports the same file and maps @theme inline to hsl(var(--…)).
  • Page Background (light): --background corresponds to about #f6f7f9; Cards (--card) and Sidebar remain white for clear contrast.
  • Primary Color: clear blue for CTAs, focus, and active sidebar elements (light/dark adjusted accordingly).
  • Radius: --radius controls rounded-* using the theme scale (radius-smradius-xl).
  • Card Shadow: --shadow-card and --shadow-card-hover in cockpit-dashboard-theme.css; Card uses shadow-[var(--shadow-card)].
  • Secondary Text: --muted-foreground (and related areas like --muted) are set so helper text and descriptions remain readable on light backgrounds (sufficient contrast to gray areas).

Changes to the “global appearance” should be made in cockpit-dashboard-theme.css (not duplicated in globals.css).

Dialog, AlertDialog & Sheet (Modals)

  • Content: bg-card / text-card-foreground (not bg-background), so the window stands out from the page area.
  • Overlay: about bg-black/55 and backdrop-blur-sm for a clear dim layer.
  • Elevation: shadow-2xl and a subtle ring for depth.
  • z-index: z-[10400] — above dropdown popovers, which often use ~9999 in the dashboard; same layer for Dialog, AlertDialog, and Sheet.

Implementation: packages/ui/src/components/ui/dialog.tsx, alert-dialog.tsx, sheet.tsx. Custom “fullscreen” overlays in the dashboard should use the same z-layer and similar card appearance, instead of z-[200] + page background color.

Dashboard Audit (manual): among others, content/hot-picks, scheduling/videos, gallery lightbox (gallery-block.plugin) — overlays at z-[10400] + bg-black/55 / backdrop-blur-sm or lightbox intentionally dark. Other apps (center-website template copies of dialog.tsx, digital-signage, center-manager) still use their own z-50 variants; update if necessary or switch to @mall-os/ui.

  • Form controls: Input, SelectTrigger, and Textarea consistently use bg-card, border-border, h-11 (Input/Select default), shadow-sm, and the same focus rings — so that search and dropdowns appear the same on gray page backgrounds. Filter bars: icon left in the trigger via flex min-w-0 flex-1 + SelectValue; search with magnifying glass icon (Search), not with filter icon.
  • Select & DropdownMenu (no layout jump): both use modal={false} by default in @mall-os/ui (select.tsx, dropdown-menu.tsx). Thus, Radix does not set a body scroll lock; the page does not jump horizontally when a menu opens. Additionally, SelectContent / DropdownMenuContent prevent browser-side scrolling to the focused portal (typical “jolt” on long pages) via onOpenAutoFocuspreventDefault(). For specific cases, modal={true} can be set (e.g., stronger focus trap). Additionally, the dashboard reserves space in globals.css with scrollbar-gutter: stable for the scrollbar.

Dashboard Page Shell (PageContainer)

Standard for content pages in the dashboard (not for hub tiles or redirects):

import { PageContainer, PageContent, PageHeader } from "@mall-os/ui";

export default function ExamplePage() {
return (
<PageContainer>
<PageHeader title="Title" description="Short description" actions={} />
<PageContent>{/* cards, tables, forms */}</PageContent>
</PageContainer>
);
}
PatternWhen
PageContainer + PageHeader + PageContentLists, detail, forms, settings
CockpitSectionHubModule hubs (Website, Staff, Organisation, Editorial flow)
SocialPageShell / SocialHubViewSocial cockpit
redirect() / CenterTabRedirectLegacy paths, tab deep links
Thin re-export (import X; export default X)Route → shared component with shell

Status badges centralized: apps/dashboard/src/lib/content-status-badges.ts — shared chip components under apps/dashboard/src/components/badges/ (e.g. template approval, QR type). Imports: always @/lib/content-status-badges, not relative ../../../lib/… paths.

Check:

node scripts/check-dashboard-page-shell.mjs # report
node scripts/check-dashboard-page-shell.mjs --strict # CI / prebuild
node scripts/check-dashboard-import-aliases.mjs --strict

Dashboard Layout Shell

The fixed chrome (sidebar inset, sticky header, content padding) is centralized in apps/dashboard/src/components/cockpit-dashboard-shell.tsx (CockpitDashboardShell, cockpitDashboardShellClasses). The dashboard layout binds this component rather than duplicating classes.

Action Buttons

ActionButton / CreateButton / … in packages/ui/src/components/ui/action-button.tsx utilize button variants (default, outline, destructive, ghost, success) and supplementary className tokens (primary, success, muted, border …) instead of fixed Tailwind colors like blue-600.

Bunny Upload Types

  • BunnyUploadFolderType is exported in packages/ui/src/hooks/use-bunny-upload.ts and describes allowed type values for uploads (including dashboard-specific folders like tile, shop-chain, themes, dooh, wayfinding, videos, images).
  • The dashboard imports this type for apps/dashboard/src/hooks/use-bunny-upload.ts and bunny-upload.tsx, so props and hooks use the same union.

Button & Dashboard-@/components/ui/*

  • Primitives: form, layout, and overlay components (Input, Card, Dialog, Tabs, Sidebar, Toast, …) are presented in the dashboard as re-exports from @mall-os/ui — an implementation under packages/ui/src/components/ui/, paths like @/components/ui/input remain valid.
  • Exceptions (local in the dashboard): bunny-upload.tsx (extended upload types + dashboard hook), simple-rich-text-editor.tsx, rich-text-editor.tsx, safe-html-content.tsx.
  • Button: packages/ui/src/components/ui/button.tsx (Button, buttonVariants); dashboard file re-exports it.
  • Tailwind: dashboard content includes ../../packages/ui/src/** so that classes from the package come through in JIT.

Core Components (Excerpt)

AreaFile under packages/ui/src/components/ui/
Buttons, Inputs, Textareabutton.tsx, input.tsx, textarea.tsx
Selectionselect.tsx, checkbox.tsx, radio-group.tsx, switch.tsx
Overlaysdialog.tsx, alert-dialog.tsx, sheet.tsx, popover.tsx
Menus & Toastsdropdown-menu.tsx, sonner.tsx, toast.tsx
Tabstabs.tsx
Cards & Layoutcard.tsx, separator.tsx, page-layout.tsx
Avataravatar.tsx
Alertsalert.tsx, alert-banner.tsx

Notes (Alert & AlertBanner)

  • Files: packages/ui/src/components/ui/alert.tsx, alert-banner.tsx.
  • Alert: shadcn pattern — rounded edge, subtle background, icon on the left, title + description. Variants: default (info/blue), destructive, warning, success. No manual border-blue-200 bg-blue-50 or similar.
  • AlertBanner: page notice with optional action on the right (button/link). Builds on Alert — for messages like “Many approvals pending”, pending drafts, setup notices.
  • Form Sections (e.g., “Quick Creation”): normal Card without border-l-4, shadow comes from card.tsx.
  • Quick Action Tiles (news list): Card with hover:shadow-md, icon in bg-primary/10 — no colored border/background combinations.
import { AlertBanner, Button } from "@mall-os/ui";

<AlertBanner
variant="warning"
title="Many approvals pending!"
description="26 news items waiting for approval."
action={<Button size="sm" variant="outline">Edit all</Button>}
/>

Simple inline notices without action: Alert + AlertTitle + AlertDescription directly.

Semantic Surfaces & Layout Building Blocks

Centralized in @mall-os/uino duplicated bg-*-50 classes in the dashboard.

ComponentFileUse
Surfacesurface.tsxHighlighted sections (info, success, warning, destructive, muted)
StatTilestat-tile.tsxKPI / metric (number + label, optional icon)
SelectableCardselectable-card.tsxSelection tiles (AI mode, quick setup)
KanbanColumnkanban-column.tsxBoard columns with token-based header/body
StatusBadgestatus-badge.tsxUnified status chips (published, pending, …)

Badge variants (outline-success, active, …) use design tokens (--success, --warning, --info) — changes in cockpit-dashboard-theme.css apply globally.

Hub Pages (CockpitSectionHub)

  • File: apps/dashboard/src/components/cockpit-section-hub.tsx
  • Tile icons via surfaceVariant + Surface from @mall-os/ui (info, success, warning, muted, …) — not via color Tailwind strings.
  • Used in: Center website, Staff, Editorial flow, Organisation.

Content Status Badges (Dashboard)

  • File: apps/dashboard/src/lib/content-status-badges.ts — badge variants (news, events, pages, jobs, offers, …)
  • File: apps/dashboard/src/lib/content-ui-tokens.ts — links (contentDetailLinkClass), metric colors (contentMetric), list icons, form helpers, detail view tokens
  • File: apps/dashboard/src/components/content/category-select-option.tsx — unified category row in create/edit selects
  • File: apps/dashboard/src/components/content/content-form-footer.tsx — fixed form footer (center, status, scheduled publishing) using the same tokens; e.g. shops, jobs, services, offices, hot picks, offers, news, events, shop locations and categories (edit). Prop showCenterSelect={false} when center assignment is fixed.

Social Hub (SocialHubView)

  • File: apps/dashboard/src/components/social/social-hub-view.tsx
  • KPI tiles and action cards use Surface from @mall-os/ui; “More” tiles share HUB_ICON_TONE with CockpitSectionHub.

Quality Assurance

pnpm check:dashboard-ui # exit 1 on violations (strict, repo root)
pnpm check:dashboard-ui-slop # AI slop patterns (spinners, side tabs, …)
pnpm check:dashboard-page-shell # page shell for dashboard/**/page.tsx
pnpm check:dashboard-import-aliases # no relative ../lib imports
pnpm --filter @mall-os/dashboard run check:ui-tokens # like prebuild
pnpm --filter @mall-os/dashboard run check:ui-slop # like prebuild
node scripts/check-dashboard-ui-tokens.mjs --strict
node scripts/check-dashboard-ui-slop.mjs --strict
node scripts/check-dashboard-import-aliases.mjs --strict
node scripts/migrate-dashboard-color-classes.mjs # codemod (optional --dry-run)

The UI token and slop scripts run automatically in dashboard prebuild with --strict. GitHub workflow .github/workflows/dashboard-quality.yml provides fast PR feedback (UI tokens, slop check, page shell, import aliases, dynamic route slug check).

Cursor rule for new dashboard UI: .cursor/rules/dashboard-ui-tokens.mdc.

Avatar

  • File: packages/ui/src/components/ui/avatar.tsx (Radix Avatar).
  • Display: root as inline-flex with items-center, justify-center, leading-none, overflow-hidden, rounded-full — avoids layout gaps due to inline-IMG baseline.
  • Image: AvatarImage with block, h-full, w-full, object-cover, object-center — circle is reliably filled; max-h-full / max-w-full against conflicts with global IMG rules (e.g., Preflight).

Switch (Toggle)

  • File: packages/ui/src/components/ui/switch.tsx.
  • Off: track bg-muted-foreground/25 (light) or /40 in dark mode, border-muted-foreground/45 — clear gray “trench”; thumb bg-white (or zinc-100 dark) with border-2, shadow-md.
  • On: track bg-primary, thumb bg-primary-foreground; slightly larger hitbox (h-7, w-[3.25rem], thumb h-6 w-6) for better left/right recognizability.

Tabs

<Tabs> uses only the segmented look (Social Cockpit) — rounded bar bg-muted/30, border, horizontal scroll when needed; inactive text-muted-foreground, active bg-background, shadow-sm, ring-1 ring-primary/40. Styles live in packages/ui/src/segmented-tabs.css (data-slot), independent of the Tailwind content scan.

Route-based sub-navigation (e.g. Social Cockpit) uses SegmentedNav / SegmentedNavLink in apps/dashboard/src/components/dashboard/segmented-nav.tsx.

import { Tabs, TabsContent, TabsList, TabsTrigger } from "@mall-os/ui";

<Tabs defaultValue="a">
<TabsList className="w-full">
<TabsTrigger value="a">One</TabsTrigger>
<TabsTrigger value="b">Two</TabsTrigger>
</TabsList>

</Tabs>

Do not override TabsList with grid / flex-wrap — spacing (mb-4, max-w-3xl) is fine.

Spinner & anti-slop

What: Unified loading indicator <Spinner /> from @mall-os/ui instead of hand-rolled border-b-2 divs, side-tab border-l-4, side-stripe divs (absolute inset-y-0 left-0 w-1 … — same slop pattern as border-l-4, implemented as a pseudo bar), gradient headings, or hardcoded bg-blue-* / bg-gray-* in core UI components.

Why: These patterns (“AI slop”) look generic, break the token system, and diverge across pages.

Where: packages/ui/src/components/ui/spinner.tsx — sizes xs | sm | default | lg | xl. In buttons: className="mr-2 text-primary-foreground" or text-destructive instead of border-white.

import { Spinner } from "@mall-os/ui";

{loading ? <Spinner className="mx-auto" /> : null}

Check: pnpm check:dashboard-ui-slop (strict in CI and dashboard prebuild). Script: scripts/check-dashboard-ui-slop.mjs.

v0 frontends (Vercel, not this repo): Impeccablenpx impeccable detect app/ components/ lib/. See v0 Instructions Part C/J.

Do not use: contentLoadingSpinnerClass / ring variants in content-ui-tokens.ts — removed in favor of <Spinner />.

Where Not to Touch

  • Center-Website Templates can have their own copies under apps/center-website/.../components/ui/ — not all changes to @mall-os/ui apply automatically there.
  • One-off layout special cases in the dashboard: prefer to set className on the page rather than duplicating new variants without need.

See Also

  • packages/ui/src/components/ui/index.ts — export list
  • packages/ui/src/cockpit-dashboard-theme.css — design tokens (SSoT)
  • packages/ui/src/styles.css — Tailwind v4 + @theme (imports the theme)

Nutzungsstatistik: Seitenaufrufe werden anonymisiert erfasst. Im Umami-Dashboard nach diesem Pfad filtern: /en/developer-guide/mall-os-ui