eddev
More Guides

Colour Schemes

Define named visual schemes with Tailwind semantic tokens.

Use colour schemes when a section, card, page, or route needs to switch the same component between named visual themes. The Tailwind helper implements this with subthemes: a scheme class changes semantic CSS variables, while component code keeps using bg-bg, text-fg, border-bg-low, and similar semantic classes.

Define Schemes

Define the possible scheme classes in tailwind.config.ts.

responsiveTheme({
  colors: {
    base: {
      black: "#202020",
      white: "#ffffff",
      silver: {
        DEFAULT: "#d8d8d8",
        light: "#f1f1f1",
        dark: "#aaaaaa",
      },
      orange: "#ea5e42",
      blue: "#2f99d4",
      green: "#4a9f62",
      card: "#dec99c",
      yellow: "#f8d44f",
    },
    semantic: {
      bg: "silver.light",
      fg: "black",
      "bg-low": "silver",
    },
    subthemes: {
      "theme-black": {
        bg: "black",
        fg: "silver.light",
        "bg-low": "silver.dark",
      },
      "theme-orange": {
        bg: "orange",
        fg: "black",
        "bg-low": "white",
      },
      "theme-blue": {
        bg: "blue",
        fg: "black",
        "bg-low": "white",
      },
      "theme-green": {
        bg: "green",
        fg: "black",
        "bg-low": "white",
      },
      "theme-card": {
        bg: "card",
        fg: "black",
        "bg-low": "white",
      },
      "theme-yellow": {
        bg: "yellow",
        fg: "black",
        "bg-low": "white",
      },
    },
  },
})

The key is the class name. Use a theme-* prefix for scheme classes unless a project has already established different naming.

Apply A Scheme

Apply the class at the smallest useful boundary. A whole page can set a scheme:

import { pageColorClass } from "@features/brand/usePageColor"
import { cn } from "@utils/tw"
import { defineView } from "eddev/views"

export default defineView("single-case-study", (props) => {
  return (
    <div className={cn(pageColorClass(props.caseStudy?.info?.pageColor))}>
      <SubpageContentBlocks blocks={props.caseStudy?.contentBlocks} />
    </div>
  )
})

A single block or card can set its own scheme:

const SCHEME_CLASSES = {
  black: "theme-black",
  orange: "theme-orange",
  blue: "theme-blue",
} as const

export function Card(props: {
  color?: keyof typeof SCHEME_CLASSES
  children: React.ReactNode
}) {
  return (
    <article className={`${SCHEME_CLASSES[props.color ?? "black"]} bg-bg text-fg p-4 rounded-md`}>
      {props.children}
    </article>
  )
}

Use generated variants when a component needs to adjust itself based on an ancestor theme:

<div className="bg-orange text-fg is-theme-black:theme-orange">
  ...
</div>

The helper creates is-${themeName}: variants for every configured subtheme.

Let Editors Choose

For editor-controlled schemes, register a small enum field in PHP and select it in the block or view GraphQL file.

<?php

ED()->registerEnumFieldType("page-scheme", [
  "label" => "Page Scheme",
  "type" => "select",
  "allow_null" => 1,
  "options" => [
    "silver" => "Silver",
    "green" => "Green",
    "orange" => "Orange",
    "blue" => "Blue",
    "card" => "Card",
    "yellow" => "Yellow",
  ],
]);
query {
  block {
    pages_highlight_section {
      theme
    }
  }
}

Map the generated enum values to classes in one helper:

import { PageSchemeOption, SectionSchemeOption } from "@generated-types"

const SCHEME_CLASSES: Record<PageSchemeOption, string> = {
  silver: "theme-silver",
  green: "theme-green",
  orange: "theme-orange",
  blue: "theme-blue",
  card: "theme-card",
  yellow: "theme-yellow",
}

export function pageColorClass(
  color: PageSchemeOption | SectionSchemeOption | null | undefined,
) {
  return SCHEME_CLASSES[color || "silver"] || SCHEME_CLASSES.silver
}

Keep the enum small. Most projects only need page-level schemes and a shorter section-level set for blocks.

Route-Level Themes

For route-driven themes, derive the current theme from RouteState and apply the scheme around RouteDisplay.

import { RouteDisplay, RouteState } from "eddev/routing"

export function ThemedRouteDisplay(props: { route: RouteState }) {
  const dark = props.route.view === "template-home"

  return (
    <div className={dark ? "theme-dark bg-bg text-fg" : "theme-light bg-bg text-fg"}>
      <RouteDisplay route={props.route} />
    </div>
  )
}

Use route-level themes for global surfaces such as headers, drawers, page transitions, and footers. Use block fields for author-controlled content sections.

On this page