Surfaces
PhoenixPaper.Typography, Divider, the pane layouts (ListDetail and SupportingPane), BottomSheet and SideSheet. MD3's base container is the Card (on the Data Display page); for any other panel, use the surface-container color roles and the rounded-pp-* corners directly.
Typography
MD3's 15 type roles (display, headline, title, body, label in large/medium/small) plus code. variant picks the style and a sensible tag; tag overrides the element independently (an h2 that looks like title-large). emphasized is M3 Expressive's bolder variant of each role, and color sets a text color role.
The type scale
display-large
display-medium
display-small
headline-large
headline-medium
headline-small
title-large
title-medium
title-small
body-large
body-medium
body-small
label-large label-medium label-smallemphasized
headline-small emphasized
title-medium emphasized
color
color="primary"
color="secondary"
color="tertiary"
color="error"
color="on-surface"
color="on-surface-variant"
| Option | Type | Default | Description |
|---|---|---|---|
color
|
"primary" | "secondary" | "tertiary" | "error" | "on-surface" | "on-surface-variant"
|
nil
|
text color role; unset inherits |
emphasized
|
boolean
|
false
|
M3 Expressive emphasized (heavier) style |
paperize
|
boolean
|
true
|
|
tag
|
"h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "p" | "span" | "div" | "label" | "legend" | "figcaption" | "code" | "strong" | "em" | "small"
|
nil
|
overrides the tag the variant picks |
variant
|
"display-large" | "display-medium" | "display-small" | "headline-large" | "headline-medium" | "headline-small" | "title-large" | "title-medium" | "title-small" | "body-large" | "body-medium" | "body-small" | "label-large" | "label-medium" | "label-small" | "code"
|
"body-large"
|
| Slot | Attrs | Description |
|---|---|---|
:inner_block
required |
|
Show code
<.pp_typography variant="display-large">Display large</.pp_typography>
<.pp_typography variant="headline-medium">Headline medium</.pp_typography>
<.pp_typography variant="title-large">Title large</.pp_typography>
<.pp_typography variant="body-large">Body large</.pp_typography>
<.pp_typography variant="label-small">Label small</.pp_typography>
<.pp_typography variant="code">mix phx.new my_app</.pp_typography>
<%!-- tag independent of variant; Expressive emphasis; a color role --%>
<.pp_typography variant="title-large" tag="h2" emphasized>Section</.pp_typography>
<.pp_typography color="on-surface-variant">Muted text</.pp_typography>
Divider
A thin outline-variant line that groups content in lists and layouts. inset indents it from the leading edge, the way MD3 separates list items that have a leading icon.
Full width and inset
Between
Below
| Option | Type | Default | Description |
|---|---|---|---|
inset
|
boolean
|
false
|
indent past a leading icon/avatar column instead of spanning full width |
paperize
|
boolean
|
true
|
Show code
<.pp_divider />
<.pp_divider inset />
Pane Layout
NewMD3's canonical layouts, from its Layout foundations: window size classes (compact under 600dp, medium to 839dp, expanded from 840dp), 16/24dp margins, a 24dp spacer and 360dp fixed panes. pp_list_detail shows both panes on expanded windows and one at a time below, chosen with show_detail (going back is yours to render). pp_supporting_pane puts a 360dp supporting pane beside the main one, or below it on smaller windows. The panes have no surface of their own: put a Card or List inside.
pp_list_detail (narrow the window to see one pane at a time)
Pick a message.
pp_supporting_pane
| Option | Type | Default | Description |
|---|---|---|---|
pp_list_detail paperize
|
boolean
|
true
|
|
pp_list_detail show_detail
|
boolean
|
false
|
compact and medium windows show the detail pane instead of the list |
pp_supporting_pane paperize
|
boolean
|
true
|
| Slot | Attrs | Description |
|---|---|---|
pp_list_detail :detail
required |
|
the detail pane |
pp_list_detail :list
required |
|
the list pane |
pp_supporting_pane :inner_block
required |
|
the main (flexible) pane |
pp_supporting_pane :supporting
required |
|
the supporting pane |
Show code
<.pp_list_detail show_detail={@message != nil}>
<:list>
<.pp_list>
<.pp_list_item :for={m <- @messages} patch={~p"/inbox/#{m.id}"} active={m.id == @message_id}>
{m.subject}
</.pp_list_item>
</.pp_list>
</:list>
<:detail>
<.pp_card :if={@message} variant="filled">
<:title>{@message.subject}</:title>
{@message.body}
<:actions>
<%!-- Below 840dp only one pane shows: going back is yours --%>
<.pp_button variant="text" patch={~p"/inbox"}>Back</.pp_button>
</:actions>
</.pp_card>
</:detail>
</.pp_list_detail>
<.pp_supporting_pane>
<article>...</article>
<:supporting>
<.pp_list>
<.pp_list_item :for={r <- @related} navigate={r.path}>{r.title}</.pp_list_item>
</.pp_list>
</:supporting>
</.pp_supporting_pane>
Bottom Sheet
MD3's bottom sheet. modal (default) slides up over a scrim with a drag handle, built like Dialog: always in the DOM, show/2 and hide/2 JS commands, focus trapping, Escape or a scrim click to dismiss. standard renders in place for persistent content docked to the bottom. With the JS hook it can be dragged down to dismiss.
Try it
| Option | Type | Default | Description |
|---|---|---|---|
drag_handle
|
boolean
|
true
|
|
id
|
string (required)
|
|
|
label
|
string
|
nil
|
aria-label of the sheet |
on_cancel
|
JS
|
%JS{}
|
|
paperize
|
boolean
|
true
|
|
show
|
boolean
|
false
|
modal: shown when first mounted |
variant
|
"modal" | "standard"
|
"modal"
|
| Slot | Attrs | Description |
|---|---|---|
:inner_block
required |
|
Show code
<.pp_button phx-click={PhoenixPaper.BottomSheet.show("share")}>Share</.pp_button>
<.pp_bottom_sheet id="share" label="Share">
<.pp_list>
<.pp_list_item phx-click="copy_link">
<:leading><.pp_icon name="hero-link" /></:leading>
Copy link
</.pp_list_item>
</.pp_list>
</.pp_bottom_sheet>
Side Sheet
MD3's side sheet at the end edge, for supplementary content and actions (filters, details). modal (default) slides in over a scrim with a header (optional back button, :title, close button) and :actions behind a divider; same show/hide JS and focus trapping as Dialog. standard renders in place as an aside beside your content.
Try it
| Option | Type | Default | Description |
|---|---|---|---|
close_label
|
string
|
"Close"
|
|
id
|
string (required)
|
|
|
on_back
|
any
|
nil
|
a JS/event for a leading back button |
on_cancel
|
JS
|
%JS{}
|
|
on_close
|
any
|
nil
|
standard sheets: a JS/event for a close button |
paperize
|
boolean
|
true
|
|
show
|
boolean
|
false
|
modal: shown when first mounted |
variant
|
"modal" | "standard"
|
"modal"
|
| Slot | Attrs | Description |
|---|---|---|
:actions
|
|
|
:inner_block
required |
|
|
:title
|
|
Show code
<.pp_button phx-click={PhoenixPaper.SideSheet.show("filters")}>Filters</.pp_button>
<.pp_side_sheet id="filters">
<:title>Filters</:title>
<.pp_checkbox name="unread" label="Unread only" />
<:actions>
<.pp_button phx-click="apply_filters">Apply</.pp_button>
<.pp_button variant="outlined" phx-click={PhoenixPaper.SideSheet.hide("filters")}>Cancel</.pp_button>
</:actions>
</.pp_side_sheet>