LiveStyle

View Source

Atomic CSS-in-Elixir for Phoenix LiveView, inspired by Meta's StyleX.

LiveStyle provides a type-safe, composable styling system with:

  • Atomic CSS: Each property-value pair becomes a single class
  • Deterministic hashing: Same styles always produce same class names
  • CSS Variables: Type-safe design tokens with vars/1
  • Constants: Static values inlined at compile time with consts/1
  • Theming: Override variables with theme/2
  • @layer support: CSS cascade layers for predictable specificity
  • Last-wins merging: Like StyleX, later styles override earlier ones

Installation

Add live_style to your dependencies in mix.exs:

def deps do
  [
    {:live_style, "~> 0.13.0"}
  ]
end

See the Getting Started guide for complete setup instructions.

Quick Example

defmodule MyAppWeb.Components.Button do
  use Phoenix.Component
  use LiveStyle

  class :base,
    display: "inline-flex",
    align_items: "center",
    padding: "8px 16px",
    border_radius: "6px"

  class :primary,
    background_color: "#4f46e5",
    color: "white",
    ":hover": [background_color: "#4338ca"]

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

Guides

Key Concepts

Design Tokens

Use vars for values that change (colors, themed tokens) and consts for static values:

defmodule MyAppWeb.Colors do
  use LiveStyle

  vars primary: "#4f46e5",
       gray_900: "#111827"
end

defmodule MyAppWeb.Spacing do
  use LiveStyle

  consts sm: "8px",
         md: "16px"
end

defmodule MyAppWeb.Animations do
  use LiveStyle

  keyframes :fade_in,
    from: [opacity: "0"],
    to: [opacity: "1"]
end

Component Styles

Reference tokens with var for colors and const for static values:

defmodule MyAppWeb.Card do
  use LiveStyle

  class :card,
    padding: const({MyAppWeb.Spacing, :md}),
    border_radius: "12px",
    color: var({MyAppWeb.Colors, :gray_900})
end

Theming

Create theme variations (only works with vars):

defmodule MyAppWeb.Semantic do
  use LiveStyle

  vars text_primary: var({MyAppWeb.Colors, :gray_900}),
       fill_page: "#ffffff"

  theme_class :dark,
    text_primary: "#ffffff",
    fill_page: var({MyAppWeb.Colors, :gray_900})
end

# In your template
<html {css(@theme == :dark && theme_class({MyAppWeb.Semantic, :dark}))}>

Pseudo-classes & Media Queries

class :link,
  color: [
    default: "blue",
    ":hover": "darkblue"
  ]

class :container,
  padding: [
    default: const({MyAppWeb.Spacing, :md}),
    "@media (min-width: 768px)": "24px"
  ]

Why LiveStyle?

vs Tailwind CSS

  • Type-safe tokens: Design tokens are Elixir values, not magic strings
  • No purging complexity: Only styles you use are generated
  • Elixir-native: Conditional logic uses && and ||, not string concatenation
  • Scoped theming: Override tokens for subtrees without global CSS

vs Inline Styles

  • Pseudo-classes: :hover, :focus, etc. work naturally
  • Media queries: Responsive design without JavaScript
  • Performance: Atomic classes are cached and deduplicated
  • DevTools: Inspect class names instead of inline style blobs

Inspired by StyleX

LiveStyle brings Meta's StyleX philosophy to Phoenix LiveView:

  • Atomic CSS for minimal bundle size
  • Last-wins merging for predictable composition
  • Deterministic class names for caching
  • CSS variables for theming

Optional Integrations

def deps do
  [
    {:live_style, "~> 0.13.0"},
    # Automatic vendor prefixing
    {:autoprefixer_ex, "~> 0.1.0"},
    # Deprecation warnings for CSS properties
    {:css_compat_data_ex, "~> 0.1.0"}
  ]
end

License

MIT License - see LICENSE for details.