LiveStyle (LiveStyle v0.12.0)

View Source

LiveStyle - Compile-time CSS-in-Elixir for Phoenix LiveView.

All style definitions compile away to string constants. At runtime, only class name strings exist - no function calls or manifest lookups.

Basic Usage

defmodule MyAppWeb.Button do
  use Phoenix.Component
  use LiveStyle

  # Define CSS variables
  vars primary: "#3b82f6",
       white: "#ffffff"

  # Define a theme that overrides variables
  theme :dark,
    primary: "#60a5fa",
    white: "#1f2937"

  # Define keyframes
  keyframes :spin,
    from: [transform: "rotate(0deg)"],
    to: [transform: "rotate(360deg)"]

  # Define classes
  class :base,
    display: "inline-flex",
    padding: "0.5rem 1rem"

  class :styled,
    background_color: var(:primary),
    color: var(:white)

  def render(assigns) do
    ~H"""
    <button {css([:base, :styled])}>
      <%= render_slot(@inner_block) %>
    </button>
    """
  end
end

Reference Syntax

Cross-module references:

  • var({Module, :name}) - Reference a CSS variable
  • const({Module, :name}) - Reference a compile-time constant
  • keyframes({Module, :name}) - Reference a keyframes animation
  • theme_class({Module, :name}) - Reference a theme class
  • position_try({Module, :name}) - Reference a position-try rule
  • view_transition({Module, :name}) - Reference a view transition

Local references (within the same module):

  • var(:name)
  • keyframes(:name)
  • theme_class(:name)

Public API Functions

See the README for comprehensive documentation and examples.

Summary

Functions

Defines a style class with CSS declarations.

References a constant, returning its raw value.

Defines compile-time constants (no CSS output).

Returns CSS attributes for spreading in HEEx templates.

Returns CSS attributes with additional inline styles merged in.

Returns the default marker class name for use with LiveStyle.When selectors.

Creates fallback values for CSS properties (StyleX firstThatWorks equivalent).

Includes styles from another class.

Runs LiveStyle CSS generation, installing dependencies if needed.

Defines a keyframes animation (2-arg form) or references one (1-arg form).

Returns a marker class name for use with LiveStyle.When selectors.

Defines or references a @position-try rule for anchor positioning.

Runs LiveStyle CSS generation.

Defines a theme (variable overrides).

References a theme, returning the class name.

References a CSS variable, returning var(--vhash).

Defines CSS custom properties (variables).

Defines a view transition.

References a view transition, returning the view-transition-class value.

Functions

class(name, declarations)

(macro)

Defines a style class with CSS declarations.

Static classes

class :button,
  display: "flex",
  padding: "8px 16px"

With variable references

class :themed,
  color: var({MyAppWeb.Tokens, :white})

Conditional styles (pseudo-classes, media queries)

class :interactive,
  color: [
    default: "blue",
    ":hover": "darkblue",
    "@media (prefers-color-scheme: dark)": "lightblue"
  ]

Conditional syntax (StyleX-style)

LiveStyle follows modern StyleX conditional syntax: conditions live inside each property's value (keyword list), rather than using top-level at-rule keys.

class :responsive_card,
  padding: [
    default: "1rem",
    "@container (min-width: 400px)": "2rem",
    "@media (min-width: 768px)": "3rem"
  ]

Dynamic classes (StyleX-style with CSS variables)

Dynamic classes use a function that declares which properties can be set at runtime. The CSS is generated with var(--x-property) references, and at runtime only the CSS variable values are set via inline style.

# Single parameter
class :dynamic_opacity, fn opacity -> [opacity: opacity] end

# Multiple parameters
class :dynamic_size, fn width, height -> [width: width, height: height] end

Usage:

<div {css([:base, {:dynamic_opacity, "0.5"}])}>
<div {css([:base, {:dynamic_size, ["100px", "200px"]}])}>

const(ref)

(macro)

References a constant, returning its raw value.

Local reference

const(:breakpoint_lg)

Cross-module reference

const({MyAppWeb.Tokens, :breakpoint_lg})

consts(consts_list)

(macro)

Defines compile-time constants (no CSS output).

Examples

consts breakpoint_sm: "@media (max-width: 640px)",
       breakpoint_lg: "@media (min-width: 1025px)",
       z_modal: "50",
       z_tooltip: "100"

css(name)

(macro)

Returns CSS attributes for spreading in HEEx templates.

Returns %LiveStyle.Attrs{} for use with the spread syntax {css(...)} in templates. This handles both static and dynamic styles that set CSS variables via inline style.

Examples

# Single ref
<div {css(:button)}>

# List of refs with conditionals
<div {css([:base, @active && :active])}>

# Dynamic styles
<div {css([{:dynamic_color, @color}])}>

# With additional inline styles
<div {css([:card], style: [view_transition_name: "card-1"])}>

# With view transitions
<div {css([:card], style: [
  view_transition_class: view_transition_class(:card),
  view_transition_name: "card-#{@id}"
])}>

css(refs, opts)

(macro)

Returns CSS attributes with additional inline styles merged in.

The second argument is a keyword list with a :style key containing additional CSS properties to merge into the inline style.

Options

  • :style - A keyword list of CSS properties to merge. Property names should be atoms (snake_case).

Examples

# With view transition styles
<div {css([:card], style: [
  view_transition_class: view_transition_class(:card),
  view_transition_name: "card-#{@id}"
])}>

# With arbitrary inline styles
<div {css([:base], style: [opacity: "0.5", transform: "scale(1.1)"])}>

default_marker()

Returns the default marker class name for use with LiveStyle.When selectors.

Example

<div class={default_marker()}>
  <div {css(:card)}>Hover parent to move me</div>
</div>

fallback(values)

@spec fallback(list()) :: {:__fallback__, list()}

Creates fallback values for CSS properties (StyleX firstThatWorks equivalent).

This function handles two cases:

  1. Regular fallbacks - Multiple declarations for browser compatibility:

     class :sticky,
       position: fallback(["sticky", "fixed"])
     # Generates: .class{position:fixed;position:sticky}
  2. CSS variable fallbacks - Nested var() with fallback values:

     class :themed,
       background_color: fallback(["var(--bg-color)", "#808080"])
     # Generates: .class{background-color:var(--bg-color, #808080)}

Values are tried in order - first value has highest priority. For CSS variables, they are nested: var(--a, var(--b, fallback)).

Examples

# Browser fallbacks (position: sticky not supported everywhere)
class :sticky,
  position: fallback(["sticky", "fixed"])

# CSS variable with fallback
class :themed,
  color: fallback(["var(--theme-color)", "blue"])

# Multiple CSS variables with final fallback
class :multi_theme,
  color: fallback(["var(--primary)", "var(--fallback)", "black"])

include(ref)

@spec include(atom() | {module(), atom()}) ::
  {:__include__, atom() | {module(), atom()}}

Includes styles from another class.

Used inside class/2 definitions for style composition. Included styles are merged with the current class using last-wins semantics - properties defined after include() override properties from included classes.

Examples

# Include a local class
class :primary, [
  include(:base),
  background_color: "blue"
]

# Include from another module
class :themed, [
  include({OtherModule, :base}),
  color: "white"
]

# Multiple includes
class :fancy, [
  include(:base),
  include(:rounded),
  include({SharedStyles, :animated}),
  border_radius: "12px"
]

install_and_run(profile \\ :default, args \\ [])

@spec install_and_run(atom(), [String.t()]) :: non_neg_integer()

Runs LiveStyle CSS generation, installing dependencies if needed.

This follows the same pattern as Tailwind.install_and_run/2 and Esbuild.install_and_run/2, making it suitable for use as a Phoenix endpoint watcher.

Setup

Add to your config/dev.exs:

config :my_app, MyAppWeb.Endpoint,
  watchers: [
    esbuild: {Esbuild, :install_and_run, [:my_app, ~w(--sourcemap=inline --watch)]},
    live_style: {LiveStyle, :install_and_run, [:default, ~w(--watch)]}
  ]

Examples

LiveStyle.install_and_run(:default, ~w(--watch))

keyframes(ref)

(macro)

keyframes(name, frames)

(macro)

Defines a keyframes animation (2-arg form) or references one (1-arg form).

Definition (2 args)

keyframes :spin,
  from: [transform: "rotate(0deg)"],
  to: [transform: "rotate(360deg)"]

keyframes :fade_in,
  "0%": [opacity: "0"],
  "100%": [opacity: "1"]

Local reference (1 arg)

keyframes(:spin)

Cross-module reference

keyframes({MyAppWeb.Tokens, :spin})

marker(name)

(macro)

Returns a marker class name for use with LiveStyle.When selectors.

Custom markers allow you to have multiple independent sets of contextual selectors in the same component tree.

Examples

# Local marker (same module)
marker(:row)

# Cross-module marker
marker({OtherModule, :row})

Usage

<tr class={marker(:row)}>
  <td {css(:cell)}>...</td>
</tr>

position_try(ref)

(macro)

position_try(name, declarations)

(macro)

Defines or references a @position-try rule for anchor positioning.

Definition (2 args)

position_try :bottom_fallback,
  top: "anchor(bottom)",
  left: "anchor(left)"

Local reference (1 arg atom)

position_try(:bottom_fallback)

Cross-module reference (1 arg tuple)

position_try({MyAppWeb.Tokens, :bottom_fallback})

run(profile \\ :default, args \\ [])

@spec run(atom(), [String.t()]) :: non_neg_integer()

Runs LiveStyle CSS generation.

This is typically called by the mix task or the watcher. Returns 0 on success.

Options

  • --watch - Watch for manifest changes and regenerate CSS automatically

Examples

LiveStyle.run(:default, [])
LiveStyle.run(:default, ~w(--watch))

theme(name, overrides)

(macro)

Defines a theme (variable overrides).

Similar to StyleX's createTheme, this creates a class that overrides CSS variables defined with vars.

Examples

# First define your variables
vars white: "#ffffff",
     primary: "#3b82f6"

# Then create a theme that overrides those variables
theme :dark,
  white: "#000000",
  primary: "#8ab4f8"

theme_class(ref)

(macro)

References a theme, returning the class name.

Local reference

theme_class(:dark)

Cross-module reference

theme_class({MyAppWeb.Tokens, :dark})

var(ref)

(macro)

References a CSS variable, returning var(--vhash).

When used as a value, returns var(--vhash) for CSS variable references. When used as a map key in keyframes, the var() wrapper is automatically stripped to produce valid CSS (matching StyleX behavior).

Local reference (same module)

var(:white)

Cross-module reference

var({MyAppWeb.Tokens, :white})

Using in keyframes (animating typed variables)

keyframes :rotate,
  from: [{var({Tokens, :angle}), "0deg"}],
  to: [{var({Tokens, :angle}), "360deg"}]

vars(vars_list)

(macro)

Defines CSS custom properties (variables).

Examples

vars white: "#ffffff",
     primary: "#3b82f6",
     spacing_sm: "0.5rem",
     spacing_lg: "2rem"

For typed variables that can be animated, use LiveStyle.Types:

import LiveStyle.Types

vars angle: angle("0deg"),
     hue: percentage("0%")

view_transition(name, styles)

(macro)

Defines a view transition.

Examples

view_transition :card_transition,
  old: [animation_name: keyframes(:fade_out), animation_duration: "250ms"],
  new: [animation_name: keyframes(:fade_in), animation_duration: "250ms"]

view_transition_class(ref)

(macro)

References a view transition, returning the view-transition-class value.

Returns the hashed class name that should be used with the CSS view-transition-class property. You control when and where to apply view-transition-name via inline styles.

Local reference

view_transition_class(:card)
# => "x9fx6z8"

Cross-module reference

view_transition_class({Tokens, :card})
# => "x9fx6z8"

Usage in templates

Use with inline styles to control view transitions:

<div style={"view-transition-class: #{view_transition_class(:card)}; view-transition-name: card-#{@id}"}>

Or use css/2 with the style option for merging with other styles:

<div {css([:card_styles], style: [view_transition_class: view_transition_class(:card), view_transition_name: "card-#{@id}"])}>