# Coolify UI design system This document defines Coolify's UI design system for its Livewire + Blade + Alpine + Tailwind v4 frontend. The visual system covers the global shell, project and environment pages, application navigation, settings surfaces, tables, modals, toasts, terminals, and metrics. Use this file as the source of truth for frontend design work. Update it in the same change whenever a new shared visual pattern or component is introduced. Onboarding validation and live server validation checkpoints share `` (idle / pending / running / success / error) inside a compact divided list, not legacy green check SVGs or fixed-width status rows. > **Maintainer rules** > > - Keep the work frontend-focused unless existing data must be exposed to the > view. > - Preserve routes, Livewire bindings, permissions, confirmations, and working > interactions while changing layout and presentation. > - Add or update tests when a UI change affects behavior. Follow the testing > requirements in `AGENTS.md`. > - Validate Blade with `docker exec coolify php artisan view:cache`, then clear > it with `docker exec coolify php artisan view:clear`. > - Build frontend assets in the Vite container with > `docker exec coolify-vite npm run build`. > - Use existing components before adding another styling abstraction. --- ## 1. Visual direction The interface is compact and product-focused: - near-neutral layered surfaces instead of large bordered boxes; - 13–14px UI typography and 32px controls; - hairline rings instead of heavy borders; - full-width data tables for dense collections; - outline Reicon glyphs through ``; - the Coolify purple brand accent in light mode; - the readable Coolify yellow accent in dark mode; - solid active-item fills (neutral black/white opacity), not accent gradients; active state is the left accent rail plus a flat selected surface; - sentence-case labels and headings; - never use the em dash (`—`) in UI copy. Prefer a period, colon, comma, or ASCII hyphen (`-`) for empty cells and separators. Avoid oversized titles, generic dashboard cards, strong shadows, thick dividers, native browser selects, and isolated colored buttons that do not match the current action styles. --- ## 2. Development and cascade notes PHP runs in the `coolify` container. The development app is normally available at `http://localhost:8000`, with Vite on port `5173`. `resources/css/app.css` still contains unlayered global element rules for headings, labels, and tables. Tailwind utilities are layered, so the unlayered rules can win unexpectedly. The settings and dense-surface CSS therefore lives as plain unlayered CSS near the end of `resources/css/app.css`, beginning at: ```css /* Coollabs layer-card settings surfaces */ ``` Important consequences: - scope settings forms with `.application-settings-form` or `.application-settings-workspace`; - add shared surface overrides to the unlayered block instead of stacking `!important` utilities; - listbox panels require ancestors with `overflow: visible`; - anchored cards use `scroll-margin-top: 7rem` to clear both fixed navigation layers; - modal shells reuse the layer-card classes but keep content-width sizing on desktop; - Alpine code inside quoted Blade attributes must not introduce conflicting quote characters. --- ## 3. Tokens and color behavior The surface ladder is defined in `resources/css/app.css`. | Token | Light | Dark | Use | |---|---|---|---| | `--coollabs-canvas` | near white | 10% neutral | page canvas | | `--coollabs-elevated` | 98% neutral | 15% neutral | shells and card headers | | `--coollabs-base` | white | 17% neutral | nested card bodies | | `--coollabs-recessed` | 96% neutral | 20% neutral | inputs and listboxes | | `--coollabs-fill` | 92.2% neutral | 26.9% neutral | dividers and passive fills | | `--coollabs-line` | translucent dark | 32% neutral | control borders | | `--coollabs-hairline` | 93.5% neutral | 26.9% neutral | shell rings | | `--coollabs-subtle` | 55.6% neutral | 70.8% neutral | labels and muted titles | Accent behavior is intentionally theme-aware: - **Light mode:** Coolify purple (`coollabs`) for active controls, focus, primary actions, and navigation accents. - **Dark mode:** Coolify yellow (`warning`) for the same states because the original purple did not provide sufficient text and ring contrast. Do not hard-code blue focus rings or leave yellow accent utilities active in light mode. Primary action patterns should normally follow: ```html bg-coollabs/10 text-coollabs ring-coollabs/25 dark:bg-warning/15 dark:text-warning dark:ring-warning/25 ``` The filled top-level action/tab treatment uses the same palette at a restrained opacity rather than a fully saturated fill. --- ## 4. Page shells and navigation ### Global shell - Main sidebar groups are compact, use outline Reicons, and keep a 32px row height. - Active sidebar rows are rounded pills (`rounded-md`) with an accent rail on the left plus a solid neutral selected fill (`bg-black/5` light, `bg-white/6` dark). Hover rows use the same radius. Do not use accent-tinted gradients on nav rows; yellow washes look muddy on dark UI. - Nested items use a thin guide line with a visible active segment, not a thick box border. - The update badge sits on the version row and uses a tiny fully rounded primary-action pill. ### Layer-2 navigation Application and server pages use the same fixed second navigation layer directly below the global topbar. Do not keep a large in-flow resource heading or legacy `.navbar-main` tabs on one resource type while using the compact layer-2 bar on another. Active tabs are a light brand fill: - purple tint in light mode; - yellow tint in dark mode; - no fully saturated tab background. Keep route-derived active state in Blade/Livewire. Do not rely only on Alpine state because it can disappear after polling or a Livewire morph. The global topbar owns the current resource identity and its compact status badges. Layer 2 owns route tabs, resource links, and contextual action buttons only. If a resource is missing from `x-top-breadcrumb`, extend the global topbar instead of repeating its name or status summary in layer 2. Mobile resource navigation may repeat this context because the desktop global topbar is hidden there. Desktop resource lifecycle actions dock in `#resource-action-hud-slot` and use ``. Show primary actions (Deploy, Redeploy, Restart, Stop) as sibling header buttons. Collapse that group into an Actions dropdown only when the remaining top-bar width cannot fit them (breadcrumb keeps a 200px floor). Infrequent operations live in a separate Advanced dropdown with the grid icon: force restart / force deploy / force cleanup on services, and Traefik dashboard / refresh proxy status on servers. Place Advanced immediately after Links, or first in the action cluster when there is no Links control. Application Deploy is a dropdown with Deploy and Deploy (without cache). A running service Restart control is a dropdown with Restart current version and Pull latest and restart. Mobile headings keep a full-width Actions dropdown because the desktop HUD is hidden below `xl`. Do not hide primary actions behind a menu on a wide desktop. Links stay a separate dropdown because the URL list is unbounded. Only add layer-2 tabs when they represent real sibling routes inside one context. Never repeat main-sidebar destinations such as Dashboard, Projects, Terminal, Servers, Sources, Destinations, or Storage as a second tab row. A single collection page does not need a tab just to fill the bar; keep its primary action in the page header instead. When tabs are useful, their left edge uses the same compact `pl-2` alignment as application navigation rather than the content container's wide horizontal padding. A layer-2 tab must be active on the page that renders it. A bar whose only tab points at a different route reads as broken navigation, so project and environment pages (`project.show`, `project.edit`, `project.environment.edit`, `project.clone-me`) carry a plain page header with a 24px title and a 13px muted summary instead of a bar. The environment identity and the way back to its resources already live in `x-top-breadcrumb`; do not restate them in a sub-header. The dashboard is a compact overview, not a metrics wall. Use two full-width sections that follow the projects-page grid pattern: projects first, then servers. Keep one `New` action in the page header and let its modal choose the resource type. Place active deployments above the resource grids as a compact, live-updating table rather than a metric card. Communicate server health with the shared status badge. ### Top-level dashboard destinations Every page opened directly from the main sidebar uses the same compact content shell: - 24px page title and a 13px muted summary; - the primary action at the top right using the restrained brand fill; - no legacy `coolbox`, `.navbar-main`, or oversized subtitle block; - four-column compact cards for small browsable collections; - a dense table instead of cards when the collection is expected to grow; - `x-empty` anatomy for empty states; - `x-status-badge` for state and `x-reicon` for all interface icons. Collection cards are `min-h-28` or `min-h-32`, use a 32px icon tile, and keep secondary metadata at 11px. They must not grow into dashboard-sized summary cards. Sources, destinations, S3 storage, private keys, and shared-variable scopes use this pattern. Top-level settings families such as Team, Notifications, Keys & Tokens, and instance Settings use a compact header followed by a small route-derived tab strip. The active tab uses the same purple-light/yellow-dark tint as resource tabs. Do not nest `