Titled card container for charts and data visualisations.
chart_shell/1 wraps any charting library output in a semantic PhiaUI
Card, providing consistent header chrome (title, description, period
label, action buttons) and a minimum-height content area — all without
coupling to a specific charting library.
When to use
Use chart_shell/1 when you want the standard PhiaUI card presentation
around a chart but need full control over the chart itself (custom hooks,
third-party libraries, canvas elements, SVG, etc.). For a batteries-included
experience, use phia_chart/1 which combines chart_shell/1 with the
PhiaChart hook automatically.
Anatomy
┌──────────────────────────────────────────────────────┐
│ card_header │
│ ┌──────────────────────────┐ ┌──────────────────┐ │
│ │ title │ │ period | actions │ │
│ │ description (optional) │ │ │ │
│ └──────────────────────────┘ └──────────────────┘ │
├──────────────────────────────────────────────────────┤
│ card_content │
│ ┌────────────────────────────────────────────────┐ │
│ │ chart content area (min-height: 300px) │ │
│ └────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────┘Basic example — custom Chart.js hook
<.chart_shell
title="Revenue Over Time"
description="Monthly recurring revenue"
period="Last 12 months"
min_height="350px"
>
<:actions>
<.button variant={:outline} size={:sm}>Export CSV</.button>
</:actions>
<canvas id="revenue-chart" phx-hook="RevenueChart" class="w-full h-full" />
</.chart_shell>Example with ECharts (manual hook wiring)
<.chart_shell title="User Growth" period="Last 90 days" min_height="400px">
<div
id="growth-chart"
phx-hook="PhiaChart"
data-config={Jason.encode!(@chart_config)}
data-series={Jason.encode!(@chart_series)}
style="height: 400px"
class="w-full"
/>
</.chart_shell>Example with multiple action buttons
<.chart_shell
title="Conversion Funnel"
description="Visitors to paying customers"
period="Q1 2026"
min_height="300px"
>
<:actions>
<.button variant={:ghost} size={:sm}><.icon name="refresh-cw" /></.button>
<.button variant={:outline} size={:sm}>Download</.button>
</:actions>
<canvas id="funnel-chart" phx-hook="FunnelChart" />
</.chart_shell>Responsive min-height
The min_height attribute accepts any CSS length: "300px", "20rem",
"50vh". The container enforces this minimum so that the chart area does
not collapse to zero before the JS hook mounts and sizes the canvas.
Library compatibility
chart_shell/1 renders a plain <div> inside its content area. Any
charting library that targets a DOM element can be used:
- ECharts via the
PhiaCharthook (built-in) - Chart.js via the
PhiaCharthook fallback or a custom hook - D3.js — mount on the container div ID
- Vega-Lite — target the inner div
- SVG charts — render inline SVG directly as inner block
Summary
Functions
Renders a titled card shell around a chart or data visualisation.
Functions
Renders a titled card shell around a chart or data visualisation.
The header layout uses a two-column flex row:
- Left: title + optional description (allows text to truncate on narrow screens)
- Right: optional period label + optional action buttons (shrink-0, never truncated)
The content area wraps the inner block in a div with min-height applied as
an inline style — this prevents the card from collapsing before the chart JS
hook initialises.
Example
<.chart_shell
title="MRR Growth"
description="Monthly recurring revenue"
period="Last 12 months"
min_height="350px"
>
<:actions>
<.button variant={:outline} size={:sm}>Export</.button>
</:actions>
<canvas id="mrr-chart" phx-hook="PhiaChart" />
</.chart_shell>Attributes
title(:string) (required) - Chart heading rendered incard_title. Always visible — use a clear, metric-focused label: "Revenue Over Time", "Active Users by Region".description(:string) - Supporting text shown below the title in smaller muted text (omitted whennil). Use for metric clarification: "Monthly recurring revenue (USD)", "Unique sessions, excluding bots".Defaults to
nil.period(:string) - Time-range label displayed in the header to the right of the title block. Typical values:"Last 30 days","Q1 2026","YTD". Omitted whennil. This is a plain string; for interactive period selectors use the:actionsslot.Defaults to
nil.min_height(:string) - Minimum CSS height of the chart content area. Prevents the container from collapsing before the JS chart hook has had time to mount and measure. Accepts any CSS length value:"300px","20rem","50vh".Defaults to
"300px".class(:string) - Additional CSS classes for the outer card element. Defaults tonil.Global attributes are accepted. HTML attributes forwarded to the outer card element (e.g.
id,data-*).
Slots
actions- Optional header action buttons rendered to the right of the period label. Use for download, refresh, period-selector, or settings buttons. Multiple actions are laid out in a horizontal flex row withgap-1between them.<:actions> <.button variant={:ghost} size={:sm}><.icon name="refresh-cw" /></.button> <.button variant={:outline} size={:sm}>Export</.button> </:actions>inner_block(required) - The chart content itself. Render any chart library output here: a<canvas>for Chart.js, a<div>for ECharts or D3, or inline SVG. The content area hasmin-heightapplied via inline style.