LiveFlow includes a theme system with 36 built-in themes and support for custom themes. Themes control the colors of the canvas background, nodes, edges, handles, text, and all UI overlays.

Built-In Themes

LiveFlow ships with 36 themes:

12 hand-crafted themes with fine-tuned colors:

light, dark, ocean, forest, sunset, synthwave, nord, autumn, cyberpunk, pastel, dracula, coffee

24 auto-generated themes mapped from daisyUI color palettes:

acid, black, luxury, retro, lofi, valentine, lemonade, garden, aqua, corporate, bumblebee, silk, dim, abyss, night, caramellatte, emerald, cupcake, cmyk, business, winter, halloween, fantasy, wireframe

Setting a Theme

Pass the theme name in the opts map of the Flow component:

<.live_component
  module={LiveFlow.Components.Flow}
  id="my-flow"
  flow={@flow}
  opts={%{theme: "dark"}}
/>

This renders the data-lf-theme="dark" attribute on the flow container, which activates the corresponding CSS theme.

Dynamic Theme Switching

Store the theme in an assign and let the user change it:

@impl true
def mount(_params, _session, socket) do
  {:ok, assign(socket, flow: create_flow(), theme: nil)}
end

@impl true
def render(assigns) do
  ~H"""
  <div class="h-screen flex flex-col">
    <div class="p-4">
      <form phx-change="change_theme">
        <select name="theme" class="select select-sm">
          <option value="">Auto</option>
          <option :for={t <- ~w(light dark ocean forest synthwave dracula)} value={t}>
            {t}
          </option>
        </select>
      </form>
    </div>
    <div class="flex-1">
      <.live_component
        module={LiveFlow.Components.Flow}
        id="my-flow"
        flow={@flow}
        opts={%{theme: @theme}}
      />
    </div>
  </div>
  """
end

@impl true
def handle_event("change_theme", %{"theme" => ""}, socket) do
  {:noreply, assign(socket, theme: nil)}
end

def handle_event("change_theme", %{"theme" => theme}, socket) do
  {:noreply, assign(socket, theme: theme)}
end

When theme is nil, LiveFlow uses the default theme (light) or inherits from the app-level dark mode via [data-theme="dark"].

Tailwind v4 Plugin Setup

LiveFlow provides a Tailwind CSS v4 plugin that registers themes using the @plugin directive. This generates CSS custom properties scoped to each theme.

Registering Themes

In your CSS file, import the themes you want:

/* assets/css/app.css */
@import "live_flow/live_flow.css";

/* Register specific themes */
@plugin "../js/live_flow/liveflow-theme" { name: "light"; default: true; }
@plugin "../js/live_flow/liveflow-theme" { name: "dark"; prefersdark: true; }
@plugin "../js/live_flow/liveflow-theme" { name: "ocean"; }
@plugin "../js/live_flow/liveflow-theme" { name: "synthwave"; }

Plugin Options

OptionDescription
nameTheme name (required). Must match a built-in theme or define custom variables.
defaultSet true to make this the default theme (uses low-specificity :where() selector).
prefersdarkSet true to activate under @media (prefers-color-scheme: dark).

How It Works

The plugin generates scoped CSS like:

/* default: true */
:where(.lf-container) {
  --lf-background: #ffffff;
  --lf-node-bg: #ffffff;
  /* ... */
}

/* Named theme */
.lf-container[data-lf-theme="dark"] {
  --lf-background: #1a1a2e;
  --lf-node-bg: #16213e;
  /* ... */
}

/* prefersdark: true */
@media (prefers-color-scheme: dark) {
  .lf-container:not([data-lf-theme]) {
    --lf-background: #1a1a2e;
    /* ... */
  }
}

CSS Custom Properties

LiveFlow themes are built on CSS custom properties. Here are the key variables:

Canvas

VariableDescription
--lf-backgroundCanvas background color
--lf-dots-colorBackground pattern dot/line color

Nodes

VariableDescription
--lf-node-bgNode background color
--lf-node-borderNode border color
--lf-node-border-selectedNode border color when selected
--lf-node-shadowNode box shadow
--lf-node-border-radiusNode border radius

Edges

VariableDescription
--lf-edge-strokeEdge line color
--lf-edge-stroke-selectedEdge color when selected
--lf-edge-label-bgEdge label background
--lf-edge-label-colorEdge label text color

Handles

VariableDescription
--lf-handle-bgHandle fill color
--lf-handle-borderHandle border color

Text

VariableDescription
--lf-text-primaryPrimary text color
--lf-text-mutedSecondary/muted text color

UI Controls

VariableDescription
--lf-controls-bgControls panel background
--lf-controls-borderControls panel border
--lf-minimap-bgMinimap background
--lf-minimap-maskMinimap mask overlay color

Interaction

VariableDescription
--lf-selection-bgSelection box fill color
--lf-selection-borderSelection box border color
--lf-accentGeneral accent/highlight color
--lf-helper-line-colorAlignment guide line color

Creating Custom Themes

Method 1: Override via the Plugin

Pass custom variable values alongside a built-in theme name:

@plugin "../js/live_flow/liveflow-theme" {
  name: "my-brand";
  --lf-background: #0a1929;
  --lf-node-bg: #0d2137;
  --lf-node-border: #1e3a5f;
  --lf-edge-stroke: #4fc3f7;
  --lf-text-primary: #e0e0e0;
  --lf-accent: #00bcd4;
}

Method 2: Pure CSS

Define variables directly in CSS, scoped to the theme attribute:

.lf-container[data-lf-theme="my-theme"] {
  --lf-background: #1a1a2e;
  --lf-node-bg: #16213e;
  --lf-node-border: #0f3460;
  --lf-node-border-selected: #e94560;
  --lf-node-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
  --lf-node-border-radius: 12px;
  --lf-edge-stroke: #533483;
  --lf-edge-stroke-selected: #e94560;
  --lf-handle-bg: #e94560;
  --lf-handle-border: #0f3460;
  --lf-text-primary: #eaeaea;
  --lf-text-muted: #a0a0a0;
  --lf-dots-color: rgba(255, 255, 255, 0.08);
  --lf-controls-bg: #16213e;
  --lf-controls-border: #0f3460;
  --lf-minimap-bg: #16213e;
  --lf-selection-bg: rgba(233, 69, 96, 0.1);
  --lf-selection-border: #e94560;
  --lf-accent: #e94560;
}

Then use it:

opts={%{theme: "my-theme"}}

Dark Mode Integration

When no explicit LiveFlow theme is set (theme: nil), the flow uses the default theme. LiveFlow provides automatic dark mode support that responds to your application-level theme:

/* App-level dark mode activates LiveFlow dark theme */
[data-theme="dark"] .lf-container:not([data-lf-theme]) {
  --lf-background: #1a1a2e;
  --lf-node-bg: #16213e;
  /* ... dark variables ... */
}

This means if your Phoenix app uses data-theme="dark" on the <html> or <body> element (common with daisyUI), LiveFlow automatically switches to dark mode without needing an explicit theme assignment.

You can also use the prefersdark plugin option to respond to the system-level dark mode preference:

@plugin "../js/live_flow/liveflow-theme" { name: "dark"; prefersdark: true; }

Tips

  • Plugin vs CSS: Use the Tailwind plugin for consistent integration with your build pipeline. Use pure CSS for quick prototyping or when not using Tailwind.
  • Specificity: The plugin uses :where(.lf-container) for the default theme (low specificity) and .lf-container[data-lf-theme="name"] for named themes. Your custom CSS with the same or higher specificity will override.
  • CSS fallbacks: LiveFlow's base stylesheet (live_flow.css) includes fallback values in @layer base. The plugin themes override these because addBase() outputs to @layer base with higher source order.
  • Custom nodes: Use var(--lf-text-primary) and other variables in your custom node components for automatic theme support (see Custom Nodes).