ShadcnEx

shadcn_ex is a Phoenix component package + tooling pipeline for automated shadcn/ui -> HEEx adaptation.

Goals

Installation

Add the dependency:

# mix.exs
def deps do
  [
    {:shadcn_ex, "~> 0.1"}
  ]
end

Run the installer to copy assets and patch your app.js/app.css:

mix shadcn.install

The installer will:

  1. Copy hooks.js and shadcn_ex.css to your app's priv/static/shadcn_ex/
  2. Patch assets/js/app.js with import statements (managed block)
  3. Patch assets/css/app.css with a Tailwind v4 @source directive for class discovery

Each patch is shown as a diff with a y/N prompt before applying.

Installer Options

Flag Description
--force Skip prompts, auto-apply all patches (CI mode)
--check Verify install completeness, exit non-zero on drift
--no-tailwind-patch Skip @source patching in app.css
--app <name> Target a specific app in an umbrella project

For umbrella projects, the installer auto-detects web apps (apps with an assets/ directory). If multiple are found, it prompts which to target. Use --app to skip the prompt.

Verifying Install in CI

mix shadcn.install --check

Returns exit code 0 if all managed blocks and assets are in place, non-zero otherwise.

Consumer Usage

  1. Optionally validate project compatibility.
  2. use ShadcnEx in LiveView/component modules.
  3. Use normal HEEx component syntax.

Optional compatibility check:

mix shadcn.validate_config

For informational-only mode (no failure on warnings):

mix shadcn.validate_config --no-strict

In your module:

defmodule MyAppWeb.PageLive do
  use MyAppWeb, :live_view
  use ShadcnEx
end

In HEEx — each shadcn sub-component maps to its own function component:

<.button variant="outline" size="sm">Save</.button>

<.card>
  <.card_title>Agents</.card_title>
  <.card_description>Runtime groups</.card_description>
  <.card_content>
    <.badge variant="secondary">Configured</.badge>
  </.card_content>
  <.card_footer>Synced just now</.card_footer>
</.card>

JS Hooks

ShadcnEx ships a tiered hook system for components that need client-side interactivity.

Tier 1 — Vanilla JS

Safe hooks using only DOM APIs and optional vanilla JS libraries:

Tier 2 — LiveView Bridges

Lightweight DOM utilities that bridge user interactions to LiveView server events:

Tier 3 — Unsupported

React-only libraries that cannot run without a React runtime. These emit a one-time console warning with guidance:

recharts, react-day-picker, react-hook-form, base-ui, cmdk, vaul

For these components, use a React adapter (e.g., LiveSvelte, LiveReact) or a native Elixir alternative.

Usage

import { initHybridComponents, setupAutoInit } from "shadcn_ex/hooks"

// Initialize all [data-js-mount] elements currently in the DOM
initHybridComponents()

// Or auto-init on LiveView mount events
setupAutoInit()

Versioning and Compatibility

mix shadcn.validate_config detects:

Current compatibility behavior:

Development

These sections are for library maintainers, not consumers.

Extract Manifest

Re-extract component metadata from upstream shadcn/ui:

mix shadcn.extract
mix shadcn.extract --style new-york-v4 --ref main

Generate Renderer Registry

Rebuild the Elixir registry from the manifest:

mix shadcn.codegen

Hardcoded Colors Audit

The extraction manifest includes a hardcoded_colors array per component, flagging Tailwind classes that use absolute colors (e.g., bg-white) rather than design tokens. Use this to audit which components may need theme overrides.

Tests

npx tsx --test scripts/extract_shadcn.test.ts
mix test