Theming

One number

Deck derives every brand colour from --hue-brand, a number from 0 to 360. Keel sets its own in public_html/css/keel.css:

@layer app.base {
  :root { --hue-brand: 38; }
}

38 is Keel's orange, #ff6b3d, expressed as a hue. Change it and the buttons, links, focus rings, badges and charts all follow, with no rebuild and no second stylesheet.

Per-tenant branding

An organization carries brand_hue. The view hands it to Deck on the <html> tag, where an inline style beats every layer:

<html <?= Deck::htmlAttributes(lang: 'en') ?> <?= Deck::theme(hue: $brandHue, mode: Theme::serverPreference()) ?>>

Two tenants on the same deploy get different palettes from that one attribute. It has to be on <html>: Deck computes its brand ramp once on :root, so setting --hue-brand on a section or a card changes nothing below it.

Keel's own CSS

Deck ships in cascade layers and leaves four of them empty for the application: app.base, app.components, app.pages and app.overrides. Everything Keel adds lives in public_html/css/keel.css inside those layers, so it beats Deck with no !important and no specificity games.

  • app.base: the brand hue, and a [hidden] rule Deck's reset leaves out.
  • app.components: .when-light and .when-dark, .table-empty, .brand-logo, .hue-swatch, .otp-input.
  • app.pages: .settings-page, .stage, and the docs rail.

Never edit public_html/deck/. Composer republishes it on every install. Building the Interface covers the layer order and why a rule in app.* beats Deck without !important.

Light and dark

  • Deck's colours are light-dark() pairs driven by color-scheme. data-theme on <html> forces one or the other.
  • views/partials/theme-toggle.php renders Deck's [data-deck-theme] button. The moon and sun swap through .when-light and .when-dark, because Deck exposes no state of its own on the button.
  • public_html/js/keel.js watches the attribute and posts to /settings/theme, so a signed-in user's choice follows them to another device.
  • A short script in views/partials/head.php applies the saved theme before the first paint, which deck.js alone does not do.

Reference