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 (paths are relative to assets/css/app.css):

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

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

Only registered themes are generated, so register every theme you pass as theme:.

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 also apply this theme to every flow under @media (prefers-color-scheme: dark).

How It Works

The plugin generates scoped CSS like:

/* default: true -- also usable by name */
:where(.lf-container), .lf-container[data-lf-theme="light"] {
  --lf-background: #f8f8f8;
  --lf-node-bg: #ffffff;
  /* ... */
}

/* Named theme */
.lf-container[data-lf-theme="dark"] {
  --lf-background: #1a1a1a;
  --lf-node-bg: #2d2d2d;
  /* ... */
}

/* prefersdark: true */
@media (prefers-color-scheme: dark) {
  .lf-container {
    --lf-background: #1a1a1a;
    /* ... */
  }
}

CSS Custom Properties

LiveFlow themes are built on CSS custom properties. Every theme sets all of them; the defaults live in live_flow.css.

Canvas

VariableDescription
--lf-backgroundCanvas background color
--lf-background-dots-colorDots pattern color (background: :dots)
--lf-background-lines-colorLines pattern color (background: :lines)
--lf-background-cross-colorCross pattern color (background: :cross)

Nodes

VariableDescription
--lf-node-bgNode background color
--lf-node-borderNode border color
--lf-node-border-radiusNode border radius
--lf-node-shadow / --lf-node-shadow-hoverNode box shadow, and on hover
--lf-node-selected-borderBorder color of selected nodes
--lf-node-selected-shadowRing around selected nodes

Edges

VariableDescription
--lf-edge-strokeEdge color (default markers follow it)
--lf-edge-stroke-widthEdge width
--lf-edge-stroke-selectedColor of selected edges (and reconnect handles)
--lf-edge-stroke-animatedColor of animated edges

Handles

VariableDescription
--lf-handle-sizeHandle diameter
--lf-handle-bgHandle fill color
--lf-handle-border / --lf-handle-border-widthHandle border
--lf-handle-hover-bgHandle fill on hover and while connecting

Text and Surfaces

VariableDescription
--lf-text-primaryPrimary text color
--lf-text-secondarySecondary text color
--lf-text-mutedMuted text color
--lf-surface-secondarySecondary surface (panels, headers)
--lf-border-secondarySecondary border color

UI Controls

VariableDescription
--lf-controls-bg / --lf-controls-borderControls panel background and border
--lf-controls-color / --lf-controls-button-hoverControls icon color and button hover background
--lf-minimap-bg / --lf-minimap-borderMinimap background and border
--lf-minimap-node-bgMinimap node color (unless minimap_node_color is set)
--lf-minimap-viewport-borderMinimap viewport outline
--lf-delete-hover-bg / --lf-delete-hover-color / --lf-delete-hover-borderDelete buttons on hover

Interaction

VariableDescription
--lf-selection-bgSelection box fill color
--lf-selection-borderSelection box border color
--lf-helper-line-colorAlignment guide line color (helper_lines: true)

Creating Custom Themes

Method 1: Override via the Plugin

Pass custom variable values to the plugin. With a new name you get a new theme (variables you don't set keep the live_flow.css defaults); with a built-in name your values override that theme:

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

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-selected-border: #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-background-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;
}

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:

/* Built into live_flow.css */
[data-theme="dark"] .lf-container:not([data-lf-theme]),
.dark .lf-container:not([data-lf-theme]) {
  --lf-background: #1a1a1a;
  --lf-node-bg: #2d2d2d;
  /* ... dark variables ... */
}

This means if your Phoenix app uses data-theme="dark" (common with daisyUI) or a dark class on an ancestor such as <html>, flows without an explicit theme switch to dark mode automatically.

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

@plugin "../../deps/live_flow/assets/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. The rest of the library CSS lives in @layer components, so your own unlayered CSS and Tailwind utilities override it without !important.
  • Custom nodes: Use var(--lf-text-primary) and other variables in your custom node components for automatic theme support (see Custom Nodes).