Components
Data Display
PhoenixPaper.Card, Avatar, Badge, Chip, Tooltip, Icon, ImageList / ImageListItem, and the Table family.
Card
A surface container with optional title and actions slots.
Basic
Padding tokens
Options
- elevation
- resting elevation, 0-24 (default: 1)
- padding
- a Spacing token (default: :md)
- shape
- corner radius token (default: :lg)
- paperize
- boolean (default: true)
<.pp_card>
<:title>Account</:title>
You have no pending invoices.
<:actions>
<.pp_button variant="text">Dismiss</.pp_button>
</:actions>
</.pp_card>
<.pp_card :for={padding <- ~w(none xs sm md lg xl 2xl)a} padding={padding}>
padding: {padding}
</.pp_card>
Avatar
A user's profile picture, initials, or icon, in the spirit of MUI's Avatar. No src falls back to the :inner_block slot (initials or an icon); no :inner_block either falls back further, to a generic person icon. A broken image also falls back to the same slot underneath it: a small vanilla onerror, no JS hook, no LiveView round-trip.
Fallback chain: image, initials, icon
variant
size
Options
- src / alt
- an image, with alt text (default: nil, falls back to :inner_block)
- variant
- circular | rounded | square (default: circular)
- size
- small | medium | large (default: medium)
- :inner_block
- initials or an icon: shown with no src, or if the image fails to load
- paperize
- boolean (default: true)
<.pp_avatar src="/images/1.jpg" alt="A profile photo" />
<.pp_avatar>OP</.pp_avatar>
<.pp_avatar />
<.pp_avatar variant="rounded">OP</.pp_avatar>
<.pp_avatar variant="square">OP</.pp_avatar>
<.pp_avatar size="small">OP</.pp_avatar>
<.pp_avatar size="large">OP</.pp_avatar>
Badge
A small count/status indicator overlapping the corner of its child, in the spirit of MUI's Badge.
Variants
Options
- content
- badge content: a number or short string (default: nil)
- max
- caps a numeric content at max+, e.g. 99+ (default: 99)
- show_zero
- show the badge when content is the integer 0 (default: false)
- variant
- standard | dot (default: standard)
- color
- primary | secondary | tertiary | error | success | warning | info (default: error)
- overlap
- rectangular | circular, pulls the badge inward onto a circular child (default: rectangular)
- anchor_origin
- which corner (default: top-right)
- invisible
- force-hide the badge (default: false)
<.pp_badge content={4}>
<.pp_icon name="hero-bell" />
</.pp_badge>
<.pp_badge content={150}>
<.pp_icon name="hero-bell" />
</.pp_badge>
<.pp_badge variant="dot" color="success">
<.pp_icon name="hero-user" />
</.pp_badge>
<%!-- overlap="circular" pulls the badge inward to sit on a circular avatar --%>
<.pp_badge content={1} overlap="circular" color="primary">
<.pp_avatar />
</.pp_badge>
Chip
A compact element for input, attribute, or action, in the spirit of MUI's Chip.
Variants
Deletable, clickable
Options
- variant
- filled | outlined (default: filled)
- color
- default | primary | secondary | tertiary | error | success | warning | info (default: default)
- size
- small | medium (default: medium)
- clickable
- renders a real button with a ripple, for filter/action chips (default: false)
- deletable
- renders a trailing delete control wired to on_delete (default: false)
- on_delete
- JS command run when the delete control is clicked
- disabled
- dims and disables the chip and its delete control (default: false)
- :icon
- a leading icon or avatar slot
<.pp_chip>Basic</.pp_chip>
<.pp_chip variant="outlined" color="primary">Outlined</.pp_chip>
<.pp_chip color="success">Success</.pp_chip>
<.pp_chip size="small">Small</.pp_chip>
<.pp_chip>
Tagged
<:icon><.pp_icon name="hero-check" /></:icon>
</.pp_chip>
<.pp_chip
:for={tag <- @chips}
deletable
on_delete={JS.push("delete_chip", value: %{chip: tag})}
>
{tag}
</.pp_chip>
<.pp_chip clickable phx-click="select_filter">Clickable</.pp_chip>
<.pp_chip clickable disabled>Disabled</.pp_chip>
Tooltip
A short text label shown on hover/focus, in the spirit of MUI's Tooltip. Pure CSS (group-hover/group-focus-within): no JS, no collision detection/auto-flip.
Try hovering
Options
- title
- the tooltip text: nil or "" disables the tooltip (default: nil)
- placement
- top | bottom | left | right (default: top)
- arrow
- a small triangle pointing at the trigger (default: false)
<.pp_tooltip title="Delete">
<.pp_button variant="icon"><.pp_icon name="hero-trash" /></.pp_button>
</.pp_tooltip>
<.pp_tooltip title="Bottom" placement="bottom">
<.pp_button variant="outlined">Bottom</.pp_button>
</.pp_tooltip>
<.pp_tooltip title="With an arrow" arrow>
<.pp_button variant="outlined">Arrow</.pp_button>
</.pp_tooltip>
Icon
Just renders the app's existing heroicon classes: no bundled icon set, no extra dependency.
Try it
Options
- name
- a heroicon class, e.g. "hero-check" (required)
- paperize
- boolean, only affects default sizing, not which icon shows (default: true)
<.pp_icon name="hero-check" class="text-pp-tertiary" />
Image List
A grid gallery of images, in the spirit of MUI's ImageList (the standard variant; masonry/quilted/woven aren't implemented). Tiles below are generated placeholder SVGs, not real photos.
cols={3}
Breakfast
Burger
Restaurant
Options
- pp_image_list cols
- 1-6 (default: 3)
- pp_image_list_item src / alt
- the image
- pp_image_list_item title / subtitle
- an overlay bar along the bottom edge, omitted if no title
<.pp_image_list cols={3}>
<.pp_image_list_item src="/images/1.jpg" title="Breakfast" />
<.pp_image_list_item src="/images/2.jpg" title="Burger" subtitle="Restaurant" />
</.pp_image_list>
Table
A family of small components: Table, TableContainer, TableHead, TableBody, TableRow, TableCell, TableFooter, composed by hand like MUI's own Table parts. dense/sticky_header/striped cascade to descendant cells via CSS, not a prop threaded through every cell.
Try it
| Fat (g) | ||
|---|---|---|
| Frozen yoghurt | 159 | 6.0 |
| Ice cream sandwich | 237 | 9.0 |
| Total | 396 | 15.0 |
dense + sticky_header (scroll the box)
| Dessert | Calories |
|---|---|
| Frozen yoghurt | 159 |
| Ice cream sandwich | 237 |
| Eclair | 262 |
| Cupcake | 305 |
| Gingerbread | 356 |
Options
- pp_table dense / sticky_header
- tighter cell padding / pins the header while scrolling
- pp_table_body striped
- alternating row background
- pp_table_row selected
- a stronger, persistent highlight
- pp_table_cell variant
- head (th) | body (td, default)
- pp_table_cell align
- left | center | right
- pp_table_cell sortable / sort_direction
- a clickable header arrow: wire your own phx-click, presentation only
<.pp_table_container>
<.pp_table>
<.pp_table_head>
<.pp_table_row>
<.pp_table_cell variant="head" sortable sort_direction="asc" phx-click="sort">Dessert</.pp_table_cell>
<.pp_table_cell variant="head" align="right" sortable phx-click="sort">Calories</.pp_table_cell>
<.pp_table_cell variant="head" align="right">Fat (g)</.pp_table_cell>
</.pp_table_row>
</.pp_table_head>
<.pp_table_body striped>
<.pp_table_row>
<.pp_table_cell>Frozen yoghurt</.pp_table_cell>
<.pp_table_cell align="right">159</.pp_table_cell>
<.pp_table_cell align="right">6.0</.pp_table_cell>
</.pp_table_row>
<.pp_table_row selected>
<.pp_table_cell>Ice cream sandwich</.pp_table_cell>
<.pp_table_cell align="right">237</.pp_table_cell>
<.pp_table_cell align="right">9.0</.pp_table_cell>
</.pp_table_row>
</.pp_table_body>
<.pp_table_footer>
<.pp_table_row>
<.pp_table_cell>Total</.pp_table_cell>
<.pp_table_cell align="right">396</.pp_table_cell>
<.pp_table_cell align="right">15.0</.pp_table_cell>
</.pp_table_row>
</.pp_table_footer>
</.pp_table>
</.pp_table_container>