PhiaUi.Components.ThemeProvider (phia_ui v0.1.16)

Copy Markdown View Source

Scoped CSS theme provider using a CSS-first, data-attribute approach.

theme_provider/1 wraps its children in a <div> and sets the data-phia-theme attribute on it. A static CSS file (phia-themes.css, generated by mix phia.theme install) defines CSS custom property overrides for each [data-phia-theme="name"] selector. Because CSS cascades, every PhiaUI component inside the wrapper automatically uses the theme's color tokens without any additional props.

Architecture: CSS-first theming

The v2 theme system is completely CSS-first — no <style> tags are injected at runtime. The flow is:

mix phia.theme install
    
    
assets/css/phia-themes.css

  [data-phia-theme="blue"] { --primary: oklch(...); ... }        
  [data-phia-theme="rose"] { --primary: oklch(...); ... }        
  ... (one block per preset × dark/light variant)                

    
     CSS cascades into children
<div data-phia-theme="blue">
  <.button>Blue primary button</.button>
  <!-- --primary resolves to the blue preset value -->
</div>

This is more efficient than runtime <style> injection because:

  • The CSS is already in the browser's CSSOM on first paint
  • No JavaScript is needed to apply theme colors
  • Multiple scoped themes on the same page work without conflict
  • Dark mode variants are handled entirely by CSS

Setup

Generate the theme CSS file:

mix phia.theme install

This creates assets/css/phia-themes.css and adds an @import to assets/css/app.css. You can also import it manually:

@import "./phia-themes.css";

Basic usage — atom shorthand

<.theme_provider theme={:blue}>
  <section class="p-4 rounded-lg">
    <.button>Blue button</.button>
    <.badge>Blue badge</.badge>
  </section>
</.theme_provider>

Usage with Theme struct

Useful when the theme is loaded from the database or application config:

# In your LiveView mount:
{:ok, theme} = PhiaUi.Theme.get(:rose)
{:ok, assign(socket, org_theme: theme)}

# In the template:
<.theme_provider theme={@org_theme}>
  <%= render_slot(@inner_block) %>
</.theme_provider>

Multiple themes on one page

Because theming is attribute-scoped, different sections of the same page can use different themes simultaneously — a powerful feature for multi-tenant UIs or theme preview pages:

<.metric_grid cols={3}>
  <.theme_provider theme={:blue}>
    <.stat_card title="Plan A" value="$12/mo" trend={:up} trend_value="+5%" />
  </.theme_provider>

  <.theme_provider theme={:rose}>
    <.stat_card title="Plan B" value="$49/mo" trend={:up} trend_value="+12%" />
  </.theme_provider>

  <.theme_provider theme={:green}>
    <.stat_card title="Plan C" value="$99/mo" trend={:up} trend_value="+3%" />
  </.theme_provider>
</.metric_grid>

Runtime theme switching

To switch the entire page theme at runtime (e.g. from a user preferences panel), use the PhiaTheme JS hook from priv/templates/js/hooks/theme.js. The hook reads the data-theme attribute on the clicked element and updates data-phia-theme on <html>, then stores the choice in localStorage['phia-color-theme'].

For a full-page theme (not scoped to a wrapper), set data-phia-theme directly on <html> from your anti-FOUC script (already handled by the unified script in the DarkModeToggle module docs).

Theme values

The :theme attribute accepts three types:

ValueResolution
:zinc, :blue, etc.Theme.get/1 is called; uses theme.name string
%PhiaUi.Theme{}Uses theme.name directly
nil (default)No data-phia-theme attribute added; inherits parent theme

Available presets

Run mix phia.theme list to see all built-in presets:

zinc | slate | blue | rose | orange | green | violet | neutral

Nesting

Themes can be nested. The inner data-phia-theme wins via CSS specificity:

<.theme_provider theme={:blue}>
  <.button>Blue</.button>
  <.theme_provider theme={:rose}>
    <.button>Rose (overrides blue)</.button>
  </.theme_provider>
</.theme_provider>

Summary

Functions

Wraps content in a scoped CSS theme context.

Functions

theme_provider(assigns)

Wraps content in a scoped CSS theme context.

Sets data-phia-theme={theme_name} on the wrapper <div>, which activates the matching CSS custom property block from phia-themes.css. All PhiaUI components inside the wrapper resolve their color tokens (primary, secondary, muted, etc.) from the theme's CSS variables.

When theme is nil, the attribute is omitted and the div is rendered as a neutral wrapper without any theme override.

Example

<.theme_provider theme={:blue} class="rounded-lg border p-4">
  <.button>Blue button</.button>
  <.badge variant={:default}>Blue badge</.badge>
</.theme_provider>

Attributes

  • theme (:any) - The theme to activate inside the wrapper. Accepts:

    • An atom matching a built-in preset (:zinc, :blue, :slate, :rose, :orange, :green, :violet, :neutral) — resolved via Theme.get/1
    • A %PhiaUi.Theme{} struct — uses theme.name directly
    • nil (default) — no data-phia-theme attribute is set; the element inherits whatever theme is set on an ancestor (or the page default)

    Defaults to nil.

  • class (:string) - Additional CSS classes applied to the wrapper <div>. The wrapper is a plain <div> with no visual styling of its own — use :class to add layout, padding, or background classes if needed.

    Defaults to nil.

  • Global attributes are accepted. HTML attributes forwarded to the wrapper div (e.g. id, style, data-*).

Slots

  • inner_block (required) - Content rendered inside the themed scope. All PhiaUI components inside will use the specified theme's CSS custom properties.