Skip to content

benvp/ex_cva

Repository files navigation

CVA

cva for elixir

Class Variance Authority for Elixir - easily construct classes with variant definitions.

Documentation hex Follow @benvp_ on Twitter

Introduction

Building out core HEEx function components like button, heading, link in general requires some way to distinguish between different appearances of the component. This is generally achieved by concatenating class strings or by extracting them into separate functions. In addition, maintaining proper definitions of supported attr :value options is required.

ex_cva aims to make this process convenient by providing a clean and maintainable way to define component variants.

Installation

Add the cva dependency to your mix.exs file.

def deps do
  [
    {:cva, "~> 0.2"}
  ]
end

Usage with HEEx function components

Configure a few variants with defaults and optionally add compound variants. CVA will take care of creating the proper class names.

One more goodie: it even creates compile-time checks for your variants to make sure all required variants are applied and contain correct values (thanks to Phoenix.Component.attr/3).

defmodule MyWeb.Components do
  use Phoenix.Component
  use CVA.Component

  variant :intent, [
      primary: "bg-cyan-600",
      secondary: "bg-zinc-700",
      destructive: "bg-red-500"
    ],
    default: :secondary

  variant :size, [
      xs: "rounded px-2.5 py-1.5 text-xs",
      sm: "rounded-md px-3 py-2 text-sm",
      md: "rounded-md px-4 py-2 text-sm",
      lg: "rounded-md px-4 py-2 text-base",
      xl: "rounded-md px-6 py-3 text-base"
    ],
    default: :md

  compound_variant "uppercase", intent: :primary, size: :xl

  attr :rest, :global
  slot :inner_block

  def button(assigns) do
    ~H"""
    <button class={@cva_class} {@rest}><%= render_slot(@inner_block) %></button>
    """
  end
end

defmodule MyWeb.SomeLive do
  import MyWeb.Components

  def render(assigns) do
    ~H"""
    <.button intent="primary">Click me</.button>
    """
  end
end

Raw cva usage

Even though ex_cva shines when working with function components, you can still use the raw cva function to generate classes.

defmodule MyCVA do
  import CVA

  def button(props) do
    config = [
      variants: [
        intent: [
          primary: "bg-cyan-600",
          secondary: "bg-zinc-700",
          destructive: "bg-red-500"
        ],
        size: [
          xs: "rounded px-2.5 py-1.5 text-xs",
          sm: "rounded-md px-3 py-2 text-sm",
          md: "rounded-md px-4 py-2 text-sm",
          lg: "rounded-md px-4 py-2 text-base",
          xl: "rounded-md px-6 py-3 text-base"
        ]
      ]
    ]

    cva(config, props)
  end
end

button(intent: :primary, size: :md) # -> "bg-cyan-600 rounded-md px-4 py-2 text-sm"

Acknowledgements

  • cva (Joe Bell) Thank you for providing the JavaScript implementation. It inspired me to port this over to Elixir. I hope you don't mind that I hijacked your logo. If you are not feeling well about that, feel free to shoot me a message.

Contributing

Contributions are very welcome. To get it up on your local machine, just check out the repo and run

mix deps.get
mix test