style_capsule

Hex.pmHex.pmTest Status

CSS scoping extension for Elixir/Phoenix components. Provides attribute-based style encapsulation for Phoenix LiveView components and standalone Elixir applications to prevent style leakage between components. Works with Phoenix and can be used standalone in other Elixir frameworks or plain Elixir scripts. Includes configurable caching strategies for optimal performance.

Migrating from the Ruby version? See MIGRATION_FROM_RUBY.md for a detailed guide.

Features

Installation

Add style_capsule to your list of dependencies in mix.exs:

def deps do
  [
    {:style_capsule, "~> 0.7.0"}
  ]
end

For Phoenix LiveView support, also ensure you have:

{:phoenix_live_view, "~> 0.20"}

Quick Start

1. Add Style Tags to Your Layout

In your Phoenix app's root layout (lib/your_app_web/components/layouts/root.html.heex):

<head>
<!-- Precompiled stylesheets (for file-based caching) -->

  <%= raw StyleCapsule.Phoenix.render_precompiled_stylesheets() %>
</head>
<body>
<!-- Your content -->

  <%= @inner_content %>
  
<!-- Runtime styles (for :none and :time cache strategies) -->

  <%= raw StyleCapsule.Phoenix.render_all_runtime_styles() %>
</body>

For page-specific stylesheets, you can conditionally load namespaces:

<head>
  <% namespace = page_namespace(assigns) %>
  <%= if namespace do %>
    <%= raw StyleCapsule.Phoenix.render_precompiled_stylesheets(namespace: namespace) %>
  <% end %>
</head>

2. Create a Component with Styles

defmodule MyAppWeb.Components.Card do
  use Phoenix.Component
  use StyleCapsule.Component

  @component_styles """
  .card { 
    padding: 1rem;
    border: 1px solid #ccc;
    border-radius: 0.5rem;
  }
  .title { 
    font-size: 1.5rem;
    font-weight: bold;
  }
  """

  def card(assigns) do
    ~H"""
    <.capsule module={__MODULE__}>
      <div class="card">
        <h2 class="title"><%= render_slot(@inner_block) %></h2>
      </div>
    </.capsule>
    """
  end
end

3. Use in LiveView

defmodule MyAppWeb.PageLive do
  use MyAppWeb, :live_view

  def render(assigns) do
    ~H"""
    <div>
      <.card>Hello from StyleCapsule</.card>
    </div>
    """
  end
end

That's it! Styles are automatically scoped and registered. No JavaScript required.

Usage

StyleCapsule automatically scopes your CSS with [data-capsule="..."] attributes and wraps your component content in a scoped element. Each component type gets a unique scope ID that's shared across all instances, ensuring styles don't leak between components.

You can customize the wrapper tag by passing tag to the capsule component:

<.capsule module={__MODULE__} tag={:section}>
<!-- your content -->

</.capsule>

Phlex Components

For Phlex components, use StyleCapsule.PhlexComponent:

defmodule MyAppWeb.Components.Card do
  use StyleCapsule.PhlexComponent

  @component_styles """
  .card { padding: 1rem; border: 1px solid #ccc; }
  .heading { font-size: 1.5rem; color: #333; }
  """

  defp render_template(assigns, attrs, state) do
    div(state, attrs, fn state ->
      h2(state, [class: "heading"], "Card Title")
    end)
  end
end

PhlexComponent automatically registers styles at compile time, adds data-capsule attributes, and generates style_capsule_spec/0 for discovery.

Standalone Usage

StyleCapsule can be used outside of Phoenix:

css = ".section { color: red; }"
capsule_id = StyleCapsule.capsule_id(MyComponent)
scoped_css = StyleCapsule.scope_css(css, capsule_id)
# => "[data-capsule="abc123"] .section { color: red; }"

html = """
<div class="section">Hello from standalone</div>
"""

wrapped_html = StyleCapsule.wrap(html, capsule_id, tag: :section, attrs: [class: "wrapper"])
# => <section data-capsule="abc123" class="wrapper"><div class="section">Hello from standalone</div></section>

For a complete, runnable script, see examples/standalone/example.exs.

CSS Scoping Strategies

StyleCapsule supports two CSS scoping strategies:

  1. Selector Patching (default): Adds [data-capsule="..."] prefix to each selector. Works in all modern browsers. Output: [data-capsule="abc123"] .section { color: red; }

  2. CSS Nesting (optional): Wraps entire CSS in [data-capsule="..."] { ... }. More performant (~3.4x faster) but requires CSS nesting support (Chrome 112+, Firefox 117+, Safari 16.5+). Output: [data-capsule="abc123"] { .section { color: red; } }

Configure the strategy when using the component:

use StyleCapsule.Component, 
  strategy: :nesting,  # Use CSS nesting
  namespace: :admin,
  cache_strategy: :time,
  cache_ttl: 3600

Caching Strategies

StyleCapsule offers multiple caching strategies to optimize performance:

use StyleCapsule.Component, 
  cache_strategy: :time, 
  cache_ttl: 3600  # Cache for 1 hour (in seconds)
use StyleCapsule.Component, cache_strategy: :file

Example Application

A complete Phoenix example application is available in examples/phoenix_demo/. It demonstrates component-scoped CSS, multiple caching strategies, namespace isolation, and Phoenix LiveView integration.

To run the example:

cd examples/phoenix_demo
mix deps.get
mix phx.server

Then visit http://localhost:4000 to see StyleCapsule in action.

Development

# Install dependencies
mix deps.get

# Run tests
mix test

# Run all quality checks
mix quality

# Format code
mix format

# Run code analysis
mix credo --strict

# Run type checking
mix dialyzer

Benchmarks

Performance benchmarks are available to measure and track StyleCapsule operations:

# Run all benchmarks
mix style_capsule.bench

# Run specific benchmark suite
mix style_capsule.bench css_processor
mix style_capsule.bench id_generation
mix style_capsule.bench cache
mix style_capsule.bench file_writer

Benchmarks generate HTML reports in benchmarks/output/ with detailed performance metrics. See benchmarks/README.md for more information.

Performance Characteristics

Based on benchmark results (Apple M3 Max, Elixir 1.18.4):

All operations are highly optimized and suitable for production use. The nesting strategy provides significant performance benefits when browser support is available.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/amkisko/style_capsule.ex

See CONTRIBUTING.md for guidelines.

Requirements

License

The library is available as open source under the terms of the MIT License.