Components
Forms
PhoenixPaper.Input, Select, NumberField, Checkbox, Switch, ThemeToggle, RadioGroup, Slider, Rating, Autocomplete, TransferList. Every one of these also accepts a field from to_form/2, the same way a generated core_components.ex input does.
Input
Modeled on MUI's TextField. Three variants (outlined, filled, standard), pure-CSS floating label, no JavaScript.
Variants
States
We'll never share your email.
is not a valid email
Colors
Size and adornments
Multiline
Options
- label / value / name / id
- standard text field attrs
- type
- any input type, e.g. text | email | password (default: text)
- variant
- outlined | filled | standard (default: outlined)
- color
- primary | secondary | tertiary | error (default: primary), focus/label accent
- size
- medium | small (default: medium)
- shape
- corner radius token (default: :sm), ignored for variant="standard"
- multiline / rows
- renders a textarea instead of an input
- start_adornment / end_adornment
- slots for prefix/suffix content, e.g. an icon or unit
- field
- a Phoenix.HTML.FormField from to_form/2: sets name/id/value for you
- errors
- list of error strings: switches to the error color, hides helper_text
- helper_text
- shown below the field when there are no errors
- disabled
- boolean (default: false)
- paperize
- boolean (default: true)
<.pp_input variant="outlined" label="Outlined (default)" name="outlined" />
<.pp_input variant="filled" label="Filled" name="filled" />
<.pp_input variant="standard" label="Standard" name="standard" />
<.pp_input label="With an error" name="error" value="not-an-email" errors={["is not a valid email"]} />
<.pp_input color="secondary" label="Secondary" name="color_secondary" />
<.pp_input size="small" label="Small" name="size_small" />
<.pp_input label="Amount" name="amount" value="42.00">
<:start_adornment>$</:start_adornment>
<:end_adornment>USD</:end_adornment>
</.pp_input>
<.pp_input multiline rows={3} label="Bio" name="bio" />
Select
A native select element styled to match Input's outlined/filled variants.
Variants
Options
- options
- list of {label, value} tuples, or plain values
- prompt
- an empty/placeholder option's label
- variant
- outlined | filled (default: outlined)
- field / errors / helper_text
- same as Input
- disabled
- boolean (default: false)
<.pp_select
label="Country"
name="country"
prompt="Choose one"
options={["Canada", "Mexico", "United States"]}
/>
<.pp_select
variant="filled"
label="Country"
name="country_filled"
prompt="Choose one"
options={["Canada", "Mexico", "United States"]}
/>
Number Field
A numeric input with increment/decrement stepper buttons: plain onclick JS calling stepUp()/stepDown(), no JS hook.
Variants
Options
- min / max / step
- passed straight to the underlying input type="number"
- variant / shape / field / errors / helper_text
- same as Input
- disabled
- boolean (default: false)
<.pp_number_field label="Quantity" name="qty" value={2} min={0} max={10} />
<.pp_number_field variant="filled" label="Quantity" name="qty_filled" value={2} min={0} max={10} />
Checkbox
Includes the hidden-input trick so an unchecked box still submits false.
States
Options
- checked
- boolean (default: nil, meaning unchecked)
- field
- a Phoenix.HTML.FormField: sets name/id/checked for you
- label
- text next to the box
- disabled
- boolean (default: false)
- ripple
- boolean, the ripple effect on click/tap (default: false)
- paperize
- false renders a bare native checkbox, no hidden input
<.pp_checkbox label="Paperized (default)" checked={true} />
<.pp_checkbox paperize={false} label="paperize: false" />
Switch
An on/off toggle, structured like Checkbox but rendered as a sliding track/thumb.
States
Options
- checked / field / label / disabled / ripple / paperize
- same shape as Checkbox
<.pp_switch label="Notifications" checked={true} name="notifications" />
Theme Toggle
A light/dark mode toggle built on top of Switch's own markup (sun/moon icons live inside the sliding thumb). Wired with a small vanilla onclick that flips data-theme on the target element, computing the effective theme itself rather than trusting the checkbox, no server round-trip needed.
Try it (flips this whole page's theme)
This site's own toggle in the top-right corner is this exact component. It doesn't persist across a full page reload by default (that's what on_toggle is for, e.g. JS.push to save the choice server-side); within a session, LiveView's own navigate-based routing keeps it in place as you move between pages, and it already falls back to the OS/browser's color-scheme preference with zero clicks, via CSS.
Options
- label
- text next to the switch (default: "Dark mode"), nil for icon-only
- default_checked
- boolean, initial visual state, uncontrolled (default: false)
- target
- CSS selector for the element to toggle data-theme on (default: "html")
- on_toggle
- extra JS commands run before the built-in flip, e.g. to persist server-side
- ripple / paperize
- same as Switch
<.pp_theme_toggle />
<%!-- accurate initial state, a scoped target, and persisting the choice server-side --%>
<.pp_theme_toggle
label="Dark mode"
default_checked={@dark_mode?}
target="#preview"
on_toggle={JS.push("save_theme_preference")}
/>
Radio Group
A labeled set of mutually exclusive radio buttons sharing one name.
Options
Options
- options
- list of {label, value} tuples, or plain values
- value
- the currently selected value
- label
- the group's legend
- ripple
- boolean, the ripple effect on click/tap (default: false)
- field / disabled / paperize
- same as other form controls
<.pp_radio_group
label="Size"
name="size"
value="md"
options={[{"Small", "sm"}, {"Medium", "md"}, {"Large", "lg"}]}
/>
Slider
A native range input, fully re-skinned via ::-webkit-slider-thumb / ::-moz-range-progress rather than CSS accent-color alone, so the unfilled portion of the track can be controlled too.
Colors
Track modes
Marks
Range, size, disabled
Vertical
Options
- min / max / step
- default 0 / 100 / 1
- value
- a number, or a {low, high} tuple for a range slider (two thumbs)
- color
- primary | secondary | tertiary | error (default: primary)
- size
- medium | small (default: medium)
- orientation
- horizontal | vertical (default: horizontal)
- track
- normal | none | inverted (default: normal), ignored for range sliders
- marks
- true (tick every step), a list of values, or a list of {value, label} tuples
- label
- shown above the slider with the current value
- field / disabled / paperize
- same as other form controls
<.pp_slider name="volume" label="Volume" value={60} />
<%!-- size --%>
<.pp_slider name="volume_small" label="Small" value={60} size="small" />
<%!-- colors --%>
<.pp_slider :for={color <- ~w(primary secondary tertiary error)} name={"volume_#{color}"} label={color} value={60} color={color} />
<%!-- track modes --%>
<.pp_slider name="volume_no_track" label="track: none" value={60} track="none" />
<.pp_slider name="volume_inverted" label="track: inverted" value={60} track="inverted" />
<%!-- discrete marks, evenly spaced --%>
<.pp_slider name="volume_marks" label="Discrete (marks)" value={40} step={20} marks={true} />
<%!-- custom labeled marks --%>
<.pp_slider
name="temperature"
label="Temperature"
value={30}
min={0}
max={100}
marks={[{0, "0°C"}, {30, "30°C"}, {60, "60°C"}, {100, "100°C"}]}
/>
<%!-- range slider: a {low, high} tuple instead of a single number --%>
<.pp_slider name="price" label="Price range" value={{20, 80}} />
<%!-- vertical --%>
<.pp_slider name="volume_vertical" orientation="vertical" value={60} />
<.pp_slider name="volume_disabled" label="Disabled" value={30} disabled />
Rating
A row of radio inputs with a pure-CSS hover/checked fill effect: hovering star 3 highlights stars 1-3, no JS.
Interactive
Read-only
Options
- value
- integer, the current/selected rating (default: 0)
- max
- number of stars (default: 5)
- readonly
- boolean, renders fixed filled/unfilled spans instead of inputs (default: false)
- field / disabled / paperize
- same as other form controls
<.pp_rating id="stars" name="stars" value={3} />
<.pp_rating readonly value={4} />
Autocomplete
A text field with a filtered dropdown, filtered entirely server-side over phx-change/phx-debounce. Unlike everything above, this needs interactive state, so it's a Phoenix.LiveComponent, fully live on this page, since it's a real LiveView. Type to filter.
Try it
Options
- options
- list of {label, value} tuples, or plain values
- value / name / label / placeholder
- same intent as Input
- shape / paperize
- same as other form controls
<.live_component
module={PhoenixPaper.Autocomplete}
id="country"
name="country"
label="Country"
placeholder="Start typing..."
options={["Canada", "Mexico", "United States", "United Kingdom", "Uruguay"]}
/>
Transfer List
Two list boxes with buttons to move checked items between them, state managed entirely inside the component. Also a Phoenix.LiveComponent: try checking a permission and moving it across.
Try it
Options
- items
- the starting list: everything begins on the left
- left_label / right_label
- column headers (default: "Available" / "Selected")
<.live_component
module={PhoenixPaper.TransferList}
id="permissions"
items={["Read", "Write", "Admin", "Billing"]}
/>