This module defines macros that generate customized components.
Usage
Add use Doggo.Components to your module and ensure you also add
use Phoenix.Component. Then use the macros in this module to generate the
components you need.
use Doggo.Components
When you use Doggo.Components, the module will import Doggo.Components
and define a __dog_components__/1 function that returns a map containing
the options of the Doggo components you used.
To generate all components with their default options:
defmodule MyAppWeb.CoreComponents do
use Doggo.Components
use Phoenix.Component
build_accordion()
build_action_bar()
build_alert()
build_alert_dialog()
build_app_bar()
build_avatar()
build_badge()
build_bottom_navigation()
build_box()
build_breadcrumb()
build_button()
build_button_link()
build_callout()
build_card()
build_carousel()
build_cluster()
build_combobox()
build_date()
build_datetime()
build_disclosure_button()
build_drawer()
build_fallback()
build_field()
build_field_group()
build_frame()
build_icon()
build_icon_sprite()
build_image()
build_menu()
build_menu_bar()
build_menu_button()
build_menu_group()
build_menu_item()
build_menu_item_checkbox()
build_menu_item_radio_group()
build_modal()
build_navbar()
build_navbar_items()
build_page_header()
build_property_list()
build_radio_group()
build_skeleton()
build_split_pane()
build_stack()
build_steps()
build_switch()
build_tab_navigation()
build_table()
build_tabs()
build_tag()
build_time()
build_toggle_button()
build_toolbar()
build_tooltip()
build_tree()
build_tree_item()
build_vertical_nav()
build_vertical_nav_nested()
build_vertical_nav_section()
endCommon Options
All component macros support the following options:
name- The name of the function of the generated component. Defaults to the macro name.base_class- The base class used on the root element of the component. If not set, a default base class is used.modifiers- A keyword list of modifier attributes. For each item, an attribute is added. The options will be passed toPhoenix.Component.attr/3. Most components define a set of default modifiers that can be overridden. Any attribute type is allowed, but since the value will be used as data attribute value, it needs to be possible to convert the value to a string. The:typeoption defaults to:string.
Some components have additional options that are mostly used to allow the customization of certain class names or to set the Gettext module.
Summary
Data
Renders a set of headings that control the visibility of their content sections.
Renders a card in an article tag, typically used repetitively in a grid or
flex box layout.
Formats a Date, DateTime, or NaiveDateTime as a date and renders it
in a <time> element.
Formats a DateTime or NaiveDateTime as a date time and renders it
in a <time> element.
The fallback component renders a given value unless it is empty, in which case it renders a fallback value instead.
Renders a list of properties as key/value pairs.
Renders a simple table.
Renders tab panels.
Renders a tag, typically used for displaying labels, categories, or keywords.
Formats a Time, DateTime, or NaiveDateTime as a time and renders it
in a <time> element.
Renders a hierarchical list as a tree.
Renders a tree item within a tree/1.
Feedback
The alert component serves as a notification mechanism to provide feedback to the user.
Renders an alert dialog that requires the immediate attention and response of the user.
Generates a badge component, typically used for drawing attention to elements like notification counts.
Renders a skeleton loader, a placeholder for content that is in the process of loading.
Form
Renders a form field including input, label, errors, and description.
Use the field group component to visually group multiple inputs in a form.
Layout
The app bar is typically located at the top of the interface and provides access to key features and navigation options.
Renders a box for a section on the page.
The cluster component is used to visually group child elements while applying a consistent gap between them.
Renders a drawer with a brand, top, and bottom slot.
Renders a header that is specific to the content of the current page.
Renders a horizontal or vertical resizable split pane.
Applies a vertical margin between the child elements.
Media
Renders profile picture, typically to represent a user.
Renders a carousel for presenting a sequence of items, such as images or text.
Renders a frame with an aspect ratio for images or videos.
Renders an icon with optional text.
Renders an icon using an SVG sprite.
Renders an image with an optional caption.
Miscellaneous
The action bar offers users quick access to primary actions within the application.
Use the callout to highlight supplementary information related to the main content.
Renders a text input with a popup that allows users to select a value from a list of suggestions.
Renders a modal dialog for content such as forms and informational panels.
Renders a group of radio buttons, for example for a toolbar.
Renders a container for a set of controls.
Renders content with a tooltip.
Data
Renders a set of headings that control the visibility of their content sections.
Maturity: Developing
The markup may change. Replacing the div, button and
aria-expanded with the platform's own <details> and <summary> is
under consideration. It would need no JavaScript, and it would change the
elements this component emits and the attributes your stylesheet targets.
Configuration
Generate the component with default options:
build_accordion()The build macro supports the common options
name, base_class, and modifiers.
Default options
[name: :accordion, base_class: "accordion", modifiers: []]Usage
<.accordion id="dog-breeds">
<:section title="Golden Retriever">
<p>
Friendly, intelligent, great with families. Origin: Scotland. Needs
regular exercise.
</p>
</:section>
<:section title="Siberian Husky">
<p>
Energetic, outgoing, distinctive appearance. Origin: Northeast Asia.
Loves cold climates.
</p>
</:section>
<:section title="Dachshund">
<p>
Playful, stubborn, small size. Origin: Germany. Enjoys sniffing games.
</p>
</:section>
</.accordion>This component needs the Doggo.Accordion JavaScript hook for the arrow
keys. See Phoenix LiveView Hooks for
registering it.
Keyboard
EnterorSpace- toggle the section of the focused header.UpandDown- move to the previous or the next header, wrapping at the ends.HomeandEnd- first and last header.
Every header is in the tab order, which is what the ARIA Authoring Practices
describe for an accordion. The arrow keys, Home and End need the
colocated hook; the ARIA Authoring Practices list them as optional for an
accordion.
Renders a card in an article tag, typically used repetitively in a grid or
flex box layout.
Maturity: Developing
Configuration
Generate the component with default options:
build_card()The build macro supports the common options
name, base_class, and modifiers.
Default options
[name: :card, base_class: "card", modifiers: []]Usage
<.card>
<:image>
<img src="image.png" alt="Picture of a dog dressed in a poncho." />
</:image>
<:header><h2>Dog Fashion Show</h2></:header>
<:body>
The next dog fashion show is coming up quickly. Here's what you need
to look out for.
</:body>
<:footer>
<span>2023-11-15 12:24</span>
<span>Events</span>
</:footer>
</.card>
Formats a Date, DateTime, or NaiveDateTime as a date and renders it
in a <time> element.
Maturity: Refining
The API of this component can be considered fairly stable, but there
are still uncertainties about accessibility aspects, such as the
handling of the <time> element and its datetime attribute by screen
readers and the limited accessibility of the title attribute.
Configuration
Generate the component with default options:
build_date()The build macro supports the common options
name, base_class, and modifiers.
Default options
[name: :date, base_class: nil, modifiers: []]Usage
By default, the given value is formatted for display with to_string/1. This:
<.date value={~D[2023-02-05]} />Will be rendered as:
<time datetime="2023-02-05">
2023-02-05
</time>You can also pass a custom formatter function. For example, if you are using ex_cldr_dates_times in your application, you could do this:
<.date
value={~D[2023-02-05]}
formatter={&MyApp.Cldr.Date.to_string!/1}
/>Which, depending on your locale, may be rendered as:
<time datetime="2023-02-05">
Feb 2, 2023
</time>If you pass a title_formatter, a title attribute is added to the
element. This can be useful if you want to render the value in a shortened
or relative format, but still give the user access to the complete value.
Note that the title attribute is only be accessible to users who use
a pointer device. Some screen readers may however announce the datetime
attribute that is always added.
<.date
value={@date}
formatter={&relative_date/1}
title_formatter={&MyApp.Cldr.Date.to_string!/1}
/>Finally, the component can shift a DateTime to a different time zone
before converting it to a date:
<.date
value={~U[2023-02-05 23:22:05Z]}
timezone="Asia/Tokyo"
/>Which would be rendered as:
<time datetime="2023-02-06">
2023-02-06
</time>
Formats a DateTime or NaiveDateTime as a date time and renders it
in a <time> element.
Maturity: Refining
The API of this component can be considered fairly stable, but there
are still uncertainties about accessibility aspects, such as the
handling of the <time> element and its datetime attribute by screen
readers and the limited accessibility of the title attribute.
Configuration
Generate the component with default options:
build_datetime()The build macro supports the common options
name, base_class, and modifiers.
Default options
[name: :datetime, base_class: nil, modifiers: []]Usage
By default, the given value is formatted for display with to_string/1. This:
<.datetime value={~U[2023-02-05 12:22:06.003Z]} />Will be rendered as:
<time datetime="2023-02-05T12:22:06.003Z">
2023-02-05 12:22:06.003Z
</time>You can also pass a custom formatter function. For example, if you are using ex_cldr_dates_times in your application, you could do this:
<.datetime
value={~U[2023-02-05 14:22:06.003Z]}
formatter={&MyApp.Cldr.DateTime.to_string!/1}
/>Which, depending on your locale, may be rendered as:
<time datetime="2023-02-05T14:22:06.003Z">
Feb 2, 2023, 14:22:06 PM
</time>The component can also truncate the value before passing it to the formatter.
<.datetime
value={~U[2023-02-05 12:22:06.003Z]}
precision={:minute}
/>If you pass a title_formatter, a title attribute is added to the
element. This can be useful if you want to render the value in a shortened
or relative format, but still give the user access to the complete value.
Note that the title attribute is only be accessible to users who use
a pointer device. Some screen readers may however announce the datetime
attribute that is always added.
<.datetime
value={@datetime}
formatter={&relative_date/1}
title_formatter={&MyApp.Cldr.DateTime.to_string!/1}
/>Finally, the component can shift a DateTime to a different time zone:
<.datetime
value={~U[2023-02-05 23:22:05Z]}
timezone="Asia/Tokyo"
/>Which would be rendered as:
<time datetime="2023-02-06T08:22:05+09:00">
2023-02-06 08:22:05+09:00 JST Asia/Tokyo
</time>
The fallback component renders a given value unless it is empty, in which case it renders a fallback value instead.
The values nil, "", [] and %{} are treated as empty values.
This component optionally applies a formatter function to non-empty values.
The primary purpose of this component is to enhance accessibility. If a table cell or property list property has no value, an alternative text must be provided for screen readers.
Maturity: Developing
Configuration
Generate the component with default options:
build_fallback()The build macro supports the common options
name, base_class, and modifiers.
Default options
[name: :fallback, base_class: "fallback", modifiers: []]Usage
Render the value of @some_value if it's available, or display the
default placeholder otherwise:
<.fallback value={@some_value} />Apply a formatter function to @some_value if it is not nil:
<.fallback value={@some_value} formatter={&format_date/1} />Set a custom placeholder and text for screen readers:
<.fallback
value={@some_value}
placeholder="n/a"
accessibility_text="not available"
/>
Renders a list of properties as key/value pairs.
This component is useful for displaying data in a structured format, such as
a list of attributes for an entity. Each property is rendered as a <dt>
element for the label and a <dd> element for the value.
Maturity: Stable
Configuration
Generate the component with default options:
build_property_list()The build macro supports the common options
name, base_class, and modifiers.
Default options
[name: :property_list, base_class: "property-list", modifiers: []]Usage
Each property is specified using the :prop slot with a label attribute
and an inner block.
<.property_list>
<:prop label={gettext("Name")}>George</:prop>
<:prop label={gettext("Age")}>42</:prop>
</.property_list>Example CSS
For example CSS, you can have a look at the demo styles.
Renders a simple table.
Maturity: Developing
Configuration
Generate the component with default options:
build_table()The build macro supports the common options
name, base_class, and modifiers.
Default options
[name: :table, base_class: "table-container", modifiers: []]Usage
<.table id="pets" rows={@pets}>
<:col :let={p} label="name"><%= p.name %></:col>
<:col :let={p} label="age"><%= p.age %></:col>
</.table>Row actions
row_click sets phx-click on each cell, which reaches pointer users only.
Where you use it, repeat the action as a link or button inside the row, so
that it can also be reached by keyboard.
<.table id="pets" rows={@pets} row_click={&JS.navigate(~p"/pets/#{&1}")}>
<:col :let={p} label="name">
<.link navigate={~p"/pets/#{p}"}><%= p.name %></.link>
</:col>
</.table>
Renders tab panels.
This component is meant for tabs that toggle content panels within the page.
If you want to link to a different view or live action, use
tab_navigation/1 instead.
Maturity: Refining
Configuration
Generate the component with default options:
build_tabs()The build macro supports the common options
name, base_class, and modifiers.
Default options
[name: :tabs, base_class: "tabs", modifiers: []]Usage
<.tabs id="dog-breed-profiles" label="Dog Breed Profiles">
<:panel label="Golden Retriever">
<p>
Friendly, intelligent, great with families. Origin: Scotland. Needs
regular exercise.
</p>
</:panel>
<:panel label="Siberian Husky">
<p>
Energetic, outgoing, distinctive appearance. Origin: Northeast Asia.
Loves cold climates.
</p>
</:panel>
<:panel label="Dachshund">
<p>
Playful, stubborn, small size. Origin: Germany. Enjoys sniffing games.
</p>
</:panel>
</.tabs>This component needs the Doggo.Tabs JavaScript hook. See
Phoenix LiveView Hooks for
registering it.
Keyboard
Left,Right,UpandDown- previous or next tab, wrapping at the ends. A tab list withorientation="horizontal"usesLeftandRight, one withorientation="vertical"usesUpandDown.HomeandEnd- first and last tab.EnterorSpace- select the focused tab.
The tab list is a single tab stop.
Renders a tag, typically used for displaying labels, categories, or keywords.
Maturity: Refining
Configuration
Generate the component with default options:
build_tag()The build macro supports the common options
name, base_class, and modifiers.
Default options
[
name: :tag,
base_class: "tag",
modifiers: [
size: [values: ["small", "normal", "medium", "large"], default: "normal"],
variant: [
values: [nil, "primary", "secondary", "info", "success", "warning",
"danger"],
default: nil
],
shape: [values: [nil, "pill"], default: nil]
]
]Usage
<.tag>Well-Trained</.tag>With icon:
<.tag>
Puppy
<.icon><Heroicons.edit /></.icon>
</.tag>With delete button:
<.tag>
High Energy
<.button
phx-click="remove-tag"
phx-value-tag="high-energy"
aria-label="Remove tag"
>
<.icon><Heroicons.x /></.icon>
</.button>
</.tag>Example CSS
For example CSS, you can have a look at the demo styles.
Formats a Time, DateTime, or NaiveDateTime as a time and renders it
in a <time> element.
Maturity: Refining
The API of this component can be considered fairly stable, but there
are still uncertainties about accessibility aspects, such as the
handling of the <time> element and its datetime attribute by screen
readers and the limited accessibility of the title attribute.
Configuration
Generate the component with default options:
build_time()The build macro supports the common options
name, base_class, and modifiers.
Default options
[name: :time, base_class: nil, modifiers: []]Usage
By default, the given value is formatted for display with to_string/1. This:
<.time value={~T[12:22:06.003Z]} />Will be rendered as:
<time datetime="12:22:06.003">
12:22:06.003
</time>You can also pass a custom formatter function. For example, if you are using ex_cldr_dates_times in your application, you could do this:
<.time
value={~T[12:22:06.003]}
formatter={&MyApp.Cldr.Time.to_string!/1}
/>Which, depending on your locale, may be rendered as:
<time datetime="14:22:06.003">
14:22:06 PM
</time>The component can also truncate the value before passing it to the formatter.
<.time
value={~U[2023-02-05 12:22:06.003Z]}
precision={:minute}
/>If you pass a title_formatter, a title attribute is added to the
element. This can be useful if you want to render the value in a shortened
or relative format, but still give the user access to the complete value.
Note that the title attribute is only be accessible to users who use
a pointer device. Some screen readers may however announce the datetime
attribute that is always added.
<.time
value={@time}
formatter={&relative_time/1}
title_formatter={&MyApp.Cldr.Time.to_string!/1}
/>Finally, the component can shift a DateTime to a different time zone:
<.time
value={~U[2023-02-05 23:22:05Z]}
timezone="Asia/Tokyo"
/>Which would be rendered as:
<time datetime="08:22:05">
08:22:05
</time>
Renders a hierarchical list as a tree.
A good use case for this component is a folder structure. For navigation and other menus, a regular nested list should be preferred.
Maturity: Developing
Missing features
- Selecting a node. The component renders
aria-selectedfrom theselectedattribute and never changes it, so the caller has to.
Configuration
Generate the component with default options:
build_tree()The build macro supports the common options
name, base_class, and modifiers.
Default options
[name: :tree, base_class: "tree", modifiers: []]Usage
<.tree label="Dogs">
<tree_item>
Breeds
<:items>
<.tree_item>Golden Retriever</.tree_item>
<.tree_item>Labrador Retriever</.tree_item>
</:items>
</.tree_item>
<.tree_item>
Characteristics
<:items>
<.tree_item>Playful</.tree_item>
<.tree_item>Loyal</.tree_item>
</:items>
</.tree_item>
</.tree>CSS
You can target the wrapper with an attribute selector for the role:
[role="tree"] {}This component needs the Doggo.Tree JavaScript hook. See
Phoenix LiveView Hooks for
registering it.
Keyboard
DownandUp- move between the visible items. A collapsed branch takes its children out of the sequence.Right- expand the focused branch, or move to its first child if it is already expanded.Left- collapse the focused branch, or move to its parent if it is already collapsed or is a leaf.HomeandEnd- first and last visible item.- Printable characters - move to the next visible item whose label starts with what you type.
The tree is a single tab stop. Expanding and collapsing is the hook's own
state, and it survives a LiveView patch. Selecting an item is not: the
component renders aria-selected from the selected attribute of
tree_item, so wire your own click or key handler and re-render.
Example CSS
For example CSS, you can have a look at the demo styles.
Renders a tree item within a tree/1.
This component can be used as a direct child of tree/1 or within the items
slot of this component.
Maturity: Developing
Missing features
- Selecting a node. The component renders
aria-selectedfrom theselectedattribute and never changes it, so the caller has to.
Configuration
Generate the component with default options:
build_tree_item()The build macro supports the common options
name, base_class, and modifiers.
Default options
[name: :tree_item, base_class: "tree-item", modifiers: []]Usage
<.tree label="Dogs">
<.tree_item>
Breeds
<:items>
<.tree_item>Golden Retriever</.tree_item>
<.tree_item>Labrador Retriever</.tree_item>
</:items>
</.tree_item>
<.tree_item>
Characteristics
<:items>
<.tree_item>Playful</.tree_item>
<.tree_item>Loyal</.tree_item>
</:items>
</.tree_item>
</.tree>Icons can be added before the label:
<.tree_item>
<Heroicon.folder /> Breeds
<:items>
<.tree_item><Heroicon.document /> Golden Retriever</.tree_item>
<.tree_item><Heroicon.document /> Labrador Retriever</.tree_item>
</:items>
</.tree_item>Example CSS
For example CSS, you can have a look at the demo styles.
Feedback
The alert component serves as a notification mechanism to provide feedback to the user.
For supplementary information that doesn't require the user's immediate
attention, use callout/1 instead.
Maturity: Developing
Configuration
Generate the component with default options:
build_alert()The build macro supports the common options
name, base_class, and modifiers.
Default options
[
name: :alert,
base_class: "alert",
modifiers: [
level: [values: ["info", "success", "warning", "danger"], default: "info"]
]
]Usage
Minimal example:
<.alert id="some-alert"></.alert>With title, icon and level:
<.alert id="some-alert" level={:info} title="Info">
message
<:icon><Heroicon.light_bulb /></:icon>
</.alert>Dismissable, with an icon in the close button:
<.alert id="some-alert" on_close={JS.push("dismiss")} close_label="Dismiss">
message
<:close><Heroicon.x_mark /></:close>
</.alert>With an action:
<.alert id="some-alert" title="Session expired">
Your session has expired. Sign in again to continue.
<:action>
<.button phx-click="sign-in">Sign in</.button>
</:action>
</.alert>The close_label is the button's accessible name, so it is needed whether
or not the :close slot is filled.
Renders an alert dialog that requires the immediate attention and response of the user.
This component is meant for situations where critical information must be conveyed, and an explicit response is required from the user. It is typically used for confirmation dialogs, warning messages, error notifications, and other scenarios where an immediate decision is necessary.
For non-critical dialogs, such as those containing forms or additional
information, use Doggo.Components.build_modal/1 instead.
Maturity: Developing
Configuration
Generate the component with default options:
build_alert_dialog()The build macro supports the common options
name, base_class, and modifiers.
Default options
[name: :alert_dialog, base_class: "alert-dialog", modifiers: []]Usage
<.alert_dialog id="end-session-modal">
<:title>End Training Session Early?</:title>
<p>
Are you sure you want to end the current training session with Bella?
She's making great progress today!
</p>
<:footer>
<.button phx-click="end-session">
Yes, end session
</.button>
<.button phx-click={JS.exec("data-cancel", to: "#end-session-modal")}>
No, continue training
</.button>
</:footer>
</.alert_dialog>To open the dialog, use the show_modal/1 function.
<.button
phx-click={Doggo.show_modal("end-session-modal")}
aria-haspopup="dialog"
>
show
</.button>With HTML attributes
command and commandfor are the Invoker Commands API. Unlike the other
two ways, this API needs no JavaScript at all.
<.button command="show-modal" commandfor="end-session-modal">show</.button>Both attributes are recent, so the hook handles them if the browser doesn't support them.
Closing
The alert dialog can be closed by:
- using
hide_modal/1, - using
JS.exec("data-cancel", to: "#end-session-modal"), which is what the example above uses for its own control, or - using the close button or
Esc(only ifdismissableis set).
Semantics
The dialog is opened with showModal(), so the browser puts it in the top
layer, draws ::backdrop, makes the rest of the document inert and keeps
the focus inside. aria-modal is not rendered, because showModal()
already marks the component as a modal.
CSS
A dialog is hidden until it is opened, so no rule is needed for that. Style
the backdrop with dialog.alert-dialog::backdrop.
Caveats
An alert dialog is not dismissable by default, so it renders
closedby="none" and no close button, which leaves no way to dismiss it
from the component. Provide your own control in the :footer slot.
Keyboard
Esc- close the dialog (only ifdismissableis set).
Example CSS
For example CSS, you can have a look at the demo styles.
Generates a badge component, typically used for drawing attention to elements like notification counts.
Maturity: Developing
Configuration
Generate the component with default options:
build_badge()The build macro supports the common options
name, base_class, and modifiers.
Default options
[
name: :badge,
base_class: "badge",
modifiers: [
size: [values: ["small", "normal", "medium", "large"], default: "normal"],
variant: [
values: [nil, "primary", "secondary", "info", "success", "warning",
"danger"],
default: nil
]
]
]Usage
<.badge>8</.badge>
Renders a skeleton loader, a placeholder for content that is in the process of loading.
It mimics the layout of the actual content, providing a better user experience during loading phases.
Maturity: Developing
This component may not address all accessibility aspects.
Configuration
Generate the component with default options:
build_skeleton()The build macro supports the common options
name, base_class, and modifiers.
Default options
[
name: :skeleton,
base_class: "skeleton",
modifiers: [
type: [
values: ["text-line", "text-block", "image", "circle", "rectangle",
"square"],
required: true
]
]
]Usage
Render one of the primitive types in isolation:
<.skeleton type="text_line" />Combine primitives for complex layouts:
<div class="card-skeleton" aria-busy="true">
<.skeleton type="image" />
<.skeleton type="text-line" />
<.skeleton type="text-line" />
<.skeleton type="text-line" />
<.skeleton type="rectangle" />
</div>To modify the primitives for your use cases, you can either configure additional modifiers or use CSS properties:
<Doggo.skeleton type="text-line" variant="header" /><Doggo.skeleton type="image" style="--aspect-ratio: 75%;" />Aria-busy attribute
When using skeleton loaders, apply aria-busy="true" to the container
element that contains the skeleton layout. For standalone use, add the
attribute directly to the individual skeleton loader.
Async result component
The easiest way to load data asynchronously and render a skeleton loader is
to use LiveView's
async operations
and Phoenix.Component.async_result/1.
Assuming you defined a card skeleton component as described above:
<.async_result :let={puppy} assign={@puppy}>
<:loading><.card_skeleton /></:loading>
<:failed :let={_reason}>There was an error loading the puppy.</:failed>
<!-- Card for loaded content -->
</.async_result>Example CSS
For example CSS, you can have a look at the demo styles.
Form
Renders a form field including input, label, errors, and description.
A Phoenix.HTML.FormField may be passed as argument,
which is used to retrieve the input name, ID, and values.
Otherwise all attributes may be passed explicitly.
Maturity: Developing
Configuration
Generate the component with default options:
build_field()In addition to the common options
name, base_class, and modifiers, the build macro
also supports the following options.
:gettext_module- If set, errors as well as therequired_textandoptional_textare automatically translated using this module. This only works if the:fieldattribute is set. Without it, errors passed to the component are rendered unchanged.:required_text- Defines a text that is rendered next to the label in required fields. Defaults to"(required)". This value is translated ifgettext_moduleis set. If you use a symbol like an asterisk, it is good practice to add a sentence explaining that fields marked with an that symbol are required.:optional_text- Defines a text that is rendered next to the label in optional fields. Defaults tonil. This value is translated ifgettext_moduleis set.:extra_types- A map from a type name to a function component that renders the control. If the field type consists of multiple inputs, it should be marked as a group:{component, group: true}. See Custom types below.
Default options
[
name: :field,
base_class: "field",
modifiers: [],
gettext_module: nil,
required_text: "(required)",
optional_text: nil,
extra_types: nil
]Usage
Custom types
You can register additional input types at build time with the extra_types
option.
build_field(
extra_types: %{"ranked" => &MyAppWeb.Inputs.ranked/1}
)The extra types can be rendered like any other types.
<.field field={@form[:rank]} type="ranked" label="Rank" />You can also use this mechanism to override the built-in types.
Types that render a group
Sometimes a control needs to render multiple inputs, for example a date
field with separate selects for each segment. Set group: true on such
types, so that the controls are wrapped inside a fieldset with a legend.
build_field(
extra_types: %{
"permissions" => {&MyAppWeb.Inputs.permissions/1, group: true}
}
)The fieldset has the class #{base_class}-#{type}. Errors and
description are rendered outside of the fieldset.
The options assign can be used to pass additional options to your custom
type. The only requirement is that it is a list.
The field component renders the label, the errors and the description as it does for any other type, and calls your function component for the control. Your component receives these assigns:
| Assign | |
|---|---|
name | the input name, with [] appended when multiple is set |
id | the id attribute, from id or the form field |
value | the field value |
type | the type you registered |
options | the options the caller passed, or nil |
prompt | the prompt the caller passed, or nil |
multiple | whether the field takes more than one value |
invalid | whether the field has errors, for aria-invalid |
describedby | for aria-describedby, nil without a description |
errormessage | for aria-errormessage, nil without errors |
validations | the validation attributes derived from the changeset |
rest | the global attributes the caller passed |
The component you referenced renders the control:
attr :name, :string, required: true
attr :id, :string, required: true
attr :value, :any, required: true
attr :type, :string, required: true
attr :options, :list, required: true
attr :prompt, :string, required: true
attr :multiple, :boolean, required: true
attr :invalid, :boolean, required: true
attr :describedby, :string, required: true
attr :errormessage, :string, required: true
attr :validations, :list, required: true
attr :rest, :global
def ranked(assigns) do
~H"""
<div class="ranked" data-type={@type}>
<select
name={@name}
id={@id}
aria-describedby={@describedby}
aria-errormessage={@errormessage}
aria-invalid={@invalid && "true"}
{@validations}
{@rest}
>
<option
:for={n <- 1..5}
value={n}
selected={to_string(n) == to_string(@value)}
>
{n}
</option>
</select>
</div>
"""
endTypes
In addition to all HTML input types, the following type values are also supported:
"select""checkbox-group""radio-group""switch"
Class and Global Attribute
Note that the class attribute is applied to the outer container, while
the rest global attribute is applied to the <input> element.
Gettext
To translate field errors as well as the required_text and optional_text
using Gettext, set the gettext_module option when building the component:
build_field(gettext_module: MyApp.Gettext)Label positioning
The component does not provide an attribute to modify label positioning directly. Instead, label positioning should be handled with CSS. If your application requires different label positions, such as horizontal and vertical layouts, it is recommended to add a modifier class to the form.
For example, the default style could position labels above inputs. To place
labels to the left of the inputs in a horizontal form layout, you can add an
is-horizontal class to the form:
<.form class="is-horizontal">
<!-- inputs -->
</.form>Then, in your CSS, apply the necessary styles to the .field class within
forms having the is-horizontal class:
form.is-horizontal .field {
// styles to position label left of the input
}The component has a hide_label attribute to visually hide labels while still
making them accessible to screen readers. If all labels within a form need to
be visually hidden, it may be more convenient to define a
.has-visually-hidden-labels modifier class for the <form>.
<.form class="has-visually-hidden-labels">
<!-- inputs -->
</.form>Ensure to take checkbox and radio labels into consideration when writing the CSS styles.
Examples
<.field field={@form[:name]} /><.field field={@form[:email]} type="email" />Radio group and checkbox group
The radio-group and checkbox-group types allow you to easily render groups
of radio buttons or checkboxes with a single component invocation. The
options attribute is required for these types and has the same format as
the options for the select type, except that options may not be nested.
<.field
field={@form[:email]}
type="checkbox-group"
label="Cuisine"
options={[
{"Mexican", "mexican"},
{"Japanese", "japanese"},
{"Libanese", "libanese"}
]}
/>Note that the checkbox-group type renders an additional hidden input with
an empty value before the checkboxes. This ensures that a value exists in case
all checkboxes are unchecked. Consequently, the resulting list value includes
an extra empty string. While Ecto.Changeset.cast/3 filters out empty strings
in array fields by default, you may need to handle the additional empty string
manual in other contexts.
Keyboard
Space- toggle a checkbox or a switch.Left,Right,UpandDown- move between the radios of a group and check the one the focus lands on.
The controls are native elements, so the browser handles these.
Use the field group component to visually group multiple inputs in a form.
This component is intended for styling purposes and does not provide semantic
grouping. For semantic grouping of related form elements, use the <fieldset>
and <legend> HTML elements instead.
Maturity: Developing
Configuration
Generate the component with default options:
build_field_group()The build macro supports the common options
name, base_class, and modifiers.
Default options
[name: :field_group, base_class: "field-group", modifiers: []]Usage
Visual grouping of inputs:
<.field_group>
<.field field={@form[:given_name]} label="Given name" />
<.field field={@form[:family_name]} label="Family name"/>
</.field_group>Semantic grouping (for reference):
<fieldset>
<legend>Personal Information</legend>
<.field field={@form[:given_name]} label="Given name" />
<.field field={@form[:family_name]} label="Family name"/>
</fieldset>
Layout
The app bar is typically located at the top of the interface and provides access to key features and navigation options.
Maturity: Experimental
Configuration
Generate the component with default options:
build_app_bar()The build macro supports the common options
name, base_class, and modifiers.
Default options
[name: :app_bar, base_class: "app-bar", modifiers: []]Usage
<.app_bar title="Page title">
<:navigation label="Open menu" on_click={JS.push("toggle-menu")}>
<.icon><Lucideicons.menu aria-hidden /></.icon>
</:navigation>
<:action label="Search" on_click={JS.push("search")}>
<.icon><Lucideicons.search aria-hidden /></.icon>
</:action>
<:action label="Like" on_click={JS.push("like")}>
<.icon><Lucideicons.heart aria-hidden /></.icon>
</:action>
</.app_bar>
Renders a box for a section on the page.
Maturity: Refining
Configuration
Generate the component with default options:
build_box()The build macro supports the common options
name, base_class, and modifiers.
Default options
[name: :box, base_class: "box", modifiers: []]Usage
Minimal example with only a box body:
<.box>
<p>This is a box.</p>
</.box>With title, banner, action, and footer:
<.box>
<:title>Profile</:title>
<:banner>
<img src="banner-image.png" alt="" />
</:banner>
<:action>
<button_link patch={~p"/profiles/#{@profile}/edit"}>Edit</button_link>
</:action>
<p>This is a profile.</p>
<:footer>
<p>Last edited: <%= @profile.updated_at %></p>
</:footer>
</.box>Example CSS
For example CSS, you can have a look at the demo styles.
The cluster component is used to visually group child elements while applying a consistent gap between them.
Common use cases are groups of buttons, groups of tags, or similar items.
The grouping is visual, so no role is set. If the children form a set that
should also be grouped for assistive technology, pass role="group" and a
label.
Maturity: Stable
Configuration
Generate the component with default options:
build_cluster()The build macro supports the common options
name, base_class, and modifiers.
Default options
[name: :cluster, base_class: "cluster", modifiers: []]Usage
<.cluster>
<div>some item</div>
<div>some other item</div>
</.cluster>With a role and a label:
<.cluster role="group" aria-label="Actions">
<.button>Edit</.button>
<.button>Delete</.button>
</.cluster>Example CSS
For example CSS, you can have a look at the demo styles.
Renders a drawer with a brand, top, and bottom slot.
All slots are optional, and you can render any content in them. If you want
to use the drawer as a sidebar, you can use the vertical_nav/1 and
vertical_nav_section/1 components.
Maturity: Developing
Configuration
Generate the component with default options:
build_drawer()The build macro supports the common options
name, base_class, and modifiers.
Default options
[name: :drawer, base_class: "drawer", modifiers: []]Usage
Minimal example:
<.drawer>
<:main>Content</:main>
</.drawer>With all slots:
<.drawer>
<:header>Doggo</:header>
<:main>Content at the top</:main>
<:footer>Content at the bottom</:footer>
</.drawer>With navigation and sections:
<.drawer>
<:header>
<.link navigate={~p"/"}>App</.link>
</:header>
<:main>
<.vertical_nav label="Main">
<:item>
<.link navigate={~p"/dashboard"}>Dashboard</.link>
</:item>
<:item>
<.vertical_nav_nested>
<:title>Content</:title>
<:item current_page>
<.link navigate={~p"/posts"}>Posts</.link>
</:item>
<:item>
<.link navigate={~p"/comments"}>Comments</.link>
</:item>
</.vertical_nav_nested>
</:item>
</.vertical_nav>
<.vertical_nav_section>
<:title>Search</:title>
<:item><input type="search" placeholder="Search" /></:item>
</.vertical_nav_section>
</:main>
<:footer>
<.vertical_nav label="User menu">
<:item>
<.link navigate={~p"/settings"}>Settings</.link>
</:item>
<:item>
<.link navigate={~p"/logout"}>Logout</.link>
</:item>
</.vertical_nav>
</:footer>
</.drawer>
Renders a header that is specific to the content of the current page.
Unlike a site-wide header, which offers consistent navigation and elements like logos throughout the website or application, this component is meant to describe the unique content of each page. For instance, on an article page, it would display the article's title.
It is typically used as a direct child of the <main> element.
Maturity: Developing
Configuration
Generate the component with default options:
build_page_header()The build macro supports the common options
name, base_class, and modifiers.
Default options
[name: :page_header, base_class: "page-header", modifiers: []]Usage
<main>
<.page_header title="Puppy Profiles" subtitle="Share Your Pup's Story">
<:action>
<.button_link patch={~p"/puppies/new"}>Add New Profile</.button_link>
</:action>
</.page_header>
<section>
<!-- Content -->
</section>
</main>With back link:
<main>
<.page_header title="Puppy Profile">
<:navigation navigate={~p"/puppies"}>
Back to puppy list
</:navigation>
<:action>
<.button_link patch={~p"/puppies/new"}>Add New Profile</.button_link>
</:action>
</.page_header>
<section>
<!-- Content -->
</section>
</main>
Renders a horizontal or vertical resizable split pane.
Maturity: Developing
Missing features
- Reporting the size to the server, so that it can be persisted
- Snapping to preset positions
- Choosing which pane keeps its size when the split pane itself is resized
Configuration
Generate the component with default options:
build_split_pane()The build macro supports the common options
name, base_class, and modifiers.
Default options
[name: :split_pane, base_class: "split-pane", modifiers: []]Usage
Vertical separator with label:
<.split_pane
id="sidebar-splitter"
label="Sidebar"
orientation="vertical"
default_size={30}
>
<:primary>One</:primary>
<:secondary>Two</:secondary>
</.split_pane>Vertical separator with visible label:
<.split_pane id="sidebar-splitter"
labelledby="sidebar-label"
orientation="vertical"
default_size={30}
>
<:primary>
<h2 id="sidebar-label">Sidebar</h2>
<p>One</p>
</:primary>
<:secondary>Two</:secondary>
</.split_pane>Nested window splitters:
<.split_pane
id="sidebar-splitter"
label="Sidebar"
orientation="vertical"
default_size={30}
>
<:primary>One</:primary>
<:secondary>
<.split_pane
id="filter-splitter"
label="Filters"
orientation="horizontal"
default_size={50}
>
<:primary>Two</:primary>
<:secondary>Three</:secondary>
</.split_pane>
</:secondary>
</.split_pane>The size of the primary pane is written to the --split-pane-position
custom property on the outer element as a percentage. Your stylesheet must
apply that property to the layout.
A split pane with a horizontal separator needs a height of its own, or both panes stay at the size of their content.
Keyboard
LeftandRight- move a vertical separator by one percent.UpandDown- move a horizontal separator by one percent.Shift+ arrow key - move by ten percent.HomeandEnd- the smallest and the largest size.Enter- collapse the primary pane, or restore the size it had before.
The size is the hook's own state and it survives a LiveView patch, so
default_size applies on the first render only.
Example CSS
For example CSS, you can have a look at the demo styles.
Applies a vertical margin between the child elements.
Maturity: Stable
Configuration
Generate the component with default options:
build_stack()The build macro supports the common options
name, base_class, and modifiers.
Default options
[name: :stack, base_class: "stack", modifiers: []]Usage
<.stack>
<div>some block</div>
<div>some other block</div>
</.stack>By default, the margin is only applied to the direct children of the
component. To apply a vertical margin on children at any nesting level, set
the recursive attribute.
<.stack recursive>
<div>
<div>some nested block</div>
<div>another nested block</div>
</div>
<div>some other block</div>
</.stack>Example CSS
For example CSS, you can have a look at the demo styles.
Media
Renders profile picture, typically to represent a user.
Maturity: Developing
Configuration
Generate the component with default options:
build_avatar()The build macro supports the common options
name, base_class, and modifiers.
Default options
[
name: :avatar,
base_class: "avatar",
modifiers: [
size: [values: ["small", "normal", "medium", "large"], default: "normal"],
shape: [values: [nil, "circle"], default: nil]
]
]Usage
Minimal example with only the src attribute:
<.avatar src="avatar.png" />Render avatar as a circle:
<.avatar src="avatar.png" circle />Use a placeholder image in case the avatar is not set:
<.avatar src={@user.avatar_url} placeholder_src="fallback.png" />Render an text as the placeholder value:
<.avatar src={@user.avatar_url} placeholder_content="A" />
Renders a carousel for presenting a sequence of items, such as images or text.
If a carousel only has a single item, no controls and no pagination are rendered.
Required CSS
The element with the -items-container class has to be a horizontal scroll
container, since the controls and the auto rotation move the carousel by
scrolling it:
.carousel-items-container {
overflow-x: auto;
scroll-snap-type: x mandatory;
}
.carousel-items {
display: flex;
}
.carousel-item {
flex: 0 0 100%;
scroll-snap-align: center;
}The items must not shrink. Scroll snapping is optional, but without it a
slide can come to rest half shown. Whether the movement is animated
depends on scroll-behavior. If you animate it, turn it off with a
@media (prefers-reduced-motion) media query.
Styling
The carousel has a data-active-index attribute, and data-paused while
the rotation is stopped. The visible slide has aria-current, its
pagination tab has aria-selected, and with loop={false} the previous and
next buttons are disabled at the ends.
The pause button changes its label, not its content. Put both icons into the
slot and use a CSS selector on data-paused to pick one:
<:pause label="Pause slide show" resume_label="Resume slide show">
<.icon name="pause" class="when-running" />
<.icon name="play" class="when-paused" />
</:pause>.carousel:not([data-paused]) .when-paused,
.carousel[data-paused] .when-running {
display: none;
}Localization
carousel_roledescription, slide_roledescription, pagination_label and
the labels of the :pause slot default to English. They are announced by
screen readers and should be translated. The pagination_slide_label and
the labels of the :previous and :next slots should also be translated.
Maturity: Developing
The semantics follow the ARIA Authoring Practices. With pagination, the
pickers are a tablist and the slides are its tab panels. Without it, the
slides are groups.
Everything the pattern asks for is implemented. The level stays at
:developing because the API is new and has not been proven in production
yet.
Configuration
Generate the component with default options:
build_carousel()The build macro supports the common options
name, base_class, and modifiers.
Default options
[name: :carousel, base_class: "carousel", modifiers: []]Usage
<.carousel label="Our Dogs">
<:previous label="Previous Slide">
<Heroicons.chevron_left />
</:previous>
<:next label="Next Slide">
<Heroicons.chevron_right />
</:next>
<:item label="1 of 3">
<.image
src="https://github.com/woylie/doggo/blob/main/assets/images/dog_1.webp?raw=true"
alt="A gray-muzzled dog in a camouflage coat and harness."
ratio="16:9"
/>
</:item>
<:item label="2 of 3">
<.image
src="https://github.com/woylie/doggo/blob/main/assets/images/dog_2.webp?raw=true"
alt="A small curly-haired white dog seen from the side."
ratio="16:9"
/>
</:item>
<:item label="3 of 3">
<.image
src="https://github.com/woylie/doggo/blob/main/assets/images/dog_3.webp?raw=true"
alt="A large cream-colored dog on a leash, looking up."
ratio="16:9"
/>
</:item>
</.carousel>This component needs the Doggo.Carousel JavaScript hook. See
Phoenix LiveView Hooks for
registering it.
Keyboard
LeftandRight- previous or next slide, with the focus on a pagination tab. Withloopthey wrap, without it they stop at the ends.HomeandEnd- first or last slide, with the focus on a pagination tab.
The pagination is a single tab stop. The arrow keys need the colocated hook.
Example CSS
For example CSS, you can have a look at the demo styles.
Renders a frame with an aspect ratio for images or videos.
Maturity: Developing
Configuration
Generate the component with default options:
build_frame()The build macro supports the common options
name, base_class, and modifiers.
Default options
[
name: :frame,
base_class: "frame",
modifiers: [
ratio: [
values: ["1:1", "3:2", "2:3", "4:3", "3:4", "5:4", "4:5", "16:9", "9:16"],
default: "1:1"
],
shape: [values: [nil, "circle"], default: nil]
]
]Usage
Rendering an image with the aspect ratio 4:3.
<.frame ratio="4:3">
<img src="image.png" alt="An example image illustrating the usage." />
</.frame>Rendering an image as a circle.
<.frame circle>
<img src="image.png" alt="An example image illustrating the usage." />
</.frame>
Renders an icon with optional text.
The component does not make assumptions about the icon library. Instead, it allows you to reference functions from libraries or custom functions that render SVG icons.
Maturity: Refining
Configuration
Generate the component with default options:
build_icon()In addition to the common options
name, base_class, and modifiers, the build macro
also supports the following options.
:icon_module(required) - The module that defines the function component(s) for rendering the icon SVG elements.:icon_fun- The name of a function component defined in the icon module that has anameattribute (string) renders the corresponding icon SVG element. If not set, the component will use the function component with the same name as the icon name and not set any attributes.:names- Either a list of available icon names or a 0-arity function that returns the list. This is only used in the generated storybook.
Default options
[
name: :icon,
base_class: "icon",
modifiers: [],
icon_module: nil,
icon_fun: nil,
names: []
]Usage
Configuration
For icon libraries that define a separate function component for each
individual icon such as heroicons, you need to set the icon_module
option.
defmodule MyAppWeb.CoreComponents do
use Doggo.Components
use Phoenix.Component
build_icon(icon_module: Heroicons)
endThe name attribute passed to the generated icon component needs to
reference a function component in the configured module.
<.icon name="bug_ant" text="report bug" />In this example, the icon component will use Heroicons.bug_ant/1 to render
the SVG icon in its inner markup.
Names are internally normalized by replacing dashes with underscores.
Therefore, both name="bug_ant" and name="bug-ant" will work.
For icon libraries that expose a single function component, you can
additionally set the icon_fun option.
build_icon(icon_module: MyIcons, icon_fun: :render)In that case, the generated icon component will pass the name attribute
on to the referenced function component.
<.icon name="circle-question" text="help" />In this example, the generated markup will be similar to:
<span class="icon">
<MyIcons.render name="circle-question" />
</span>Text display
Render an icon with visually hidden text:
<.icon name="bug_ant" text="report bug" />To display the text visibly:
<.icon name="bug_ant" text="report bug" text_position="after" />Or:
<.icon name="bug_ant" text="report bug" text_position="before" />The text_position attribute values are chosen to work with both
left-to-right and right-to-left languages. Refer to the CSS example for
applying the position correctly.
aria-hidden
Not all icon libraries set the aria-hidden attribute by default. Always
make sure that it is set on the <svg> element that the library renders.
Example CSS
For example CSS, you can have a look at the demo styles.
Renders an icon using an SVG sprite.
Maturity: Refining
Configuration
Generate the component with default options:
build_icon_sprite()In addition to the common options
name, base_class, and modifiers, the build macro
also supports the following options.
:sprite_url- URL of the icon sprite.
Default options
[
name: :icon_sprite,
base_class: "icon",
modifiers: [],
sprite_url: "/assets/icons/sprite.svg"
]Usage
Render an icon with visually hidden text:
<.icon name="arrow-left" text="Go back" />To display the text visibly:
<.icon name="arrow-left" text="Go back" text_position={:right} />Example CSS
For example CSS, you can have a look at the demo styles.
Renders an image with an optional caption.
Maturity: Developing
Configuration
Generate the component with default options:
build_image()The build macro supports the common options
name, base_class, and modifiers.
Default options
[
name: :image,
base_class: "image",
modifiers: [
ratio: [
values: [nil, "1:1", "3:2", "2:3", "4:3", "3:4", "5:4", "4:5", "16:9",
"9:16"],
default: nil
]
]
]Usage
<.image
src="https://github.com/woylie/doggo/blob/main/assets/images/dog_1.webp?raw=true"
alt="A gray-muzzled dog in a camouflage coat and harness."
ratio={{16, 9}}
>
<:caption>
Spotlight on canine couture: A dog fashion show where four-legged models
dazzle the runway with the latest in pet apparel.
</:caption>
</.image>
Miscellaneous
The action bar offers users quick access to primary actions within the application.
It is typically positioned to float above other content.
Maturity: Developing
Configuration
Generate the component with default options:
build_action_bar()The build macro supports the common options
name, base_class, and modifiers.
Default options
[name: :action_bar, base_class: "action-bar", modifiers: []]Usage
<.action_bar>
<:item label="Edit" on_click={JS.push("edit")}>
<.icon><Lucideicons.pencil aria-hidden /></.icon>
</:item>
<:item label="Move" on_click={JS.push("move")}>
<.icon><Lucideicons.move aria-hidden /></.icon>
</:item>
<:item label="Archive" on_click={JS.push("archive")}>
<.icon><Lucideicons.archive aria-hidden /></.icon>
</:item>
</.action_bar>This component needs the Doggo.Toolbar JavaScript hook. See
Phoenix LiveView Hooks for
registering it.
Keyboard
LeftandRight- move between the buttons, wrapping at the ends.HomeandEnd- first and last button.EnterorSpace- run the focused button'son_click.
The action bar is a single tab stop. Tab moves to the button the user last
used, and the arrow keys move between them.
Example CSS
For example CSS, you can have a look at the demo styles.
Use the callout to highlight supplementary information related to the main content.
For information that needs immediate attention of the user, use alert/1
instead.
Maturity: Developing
Configuration
Generate the component with default options:
build_callout()The build macro supports the common options
name, base_class, and modifiers.
Default options
[
name: :callout,
base_class: "callout",
modifiers: [
level: [values: ["info", "success", "warning", "danger"], default: "info"]
]
]Usage
Standard callout:
<.callout id="callout-dog-care-tip" title="Dog Care Tip">
<p>Regular exercise is essential for keeping your dog healthy and happy.</p>
</.callout>Callout with an icon:
<.callout id="callout-fun-dog-fact" title="Fun Dog Fact">
<:icon><Heroicons.information_circle /></:icon>
<p>
Did you know? Dogs have a sense of time and can get upset when their
routine is changed.
</p>
</.callout>Callout with an action:
<.callout id="callout-fun-dog-fact" title="Fun Dog Fact">
<p>
Did you know? Dogs have a sense of time and can get upset when their
routine is changed.
</p>
<:action><.link>Learn More</.link></:action>
</.callout>
Renders a text input with a popup that allows users to select a value from a list of suggestions.
Maturity: Developing
The semantics follow the ARIA Authoring Practices, and everything the combobox pattern asks for is implemented, including the keyboard support.
The level stays at :developing because the API is new and has not been
proven in production yet.
Configuration
Generate the component with default options:
build_combobox()The build macro supports the common options
name, base_class, and modifiers.
Default options
[name: :combobox, base_class: "combobox", modifiers: []]Usage
Options
With simple values:
<.combobox
id="dog-breed-selector"
name="breed"
list_label="Dog breeds"
options={[
"Labrador Retriever",
"German Shepherd",
"Golden Retriever",
"French Bulldog",
"Bulldog"
]}
/>With label/value pairs:
<.combobox
id="dog-breed-selector"
name="breed"
list_label="Dog breeds"
options={[
{"Labrador Retriever", "labrador"},
{"German Shepherd", "german_shepherd"},
{"Golden Retriever", "golden_retriever"},
{"French Bulldog", "french_bulldog"},
{"Bulldog", "bulldog"}
]}
/>With descriptions and a disabled option:
<.combobox
id="dog-breed-selector"
name="breed"
list_label="Dog breeds"
options={[
[key: "Labrador Retriever", value: "labrador", description: "Friendly and outgoing"],
[key: "German Shepherd", value: "german_shepherd", description: "Confident and smart"],
[key: "Bulldog", value: "bulldog", description: "Docile and willful", disabled: true]
]}
/>Label
The component does not render a label by itself, so you must render one yourself. This is important for accessibility: without it the text input has no accessible name, and a screen reader announces it as a combobox without saying what is being chosen.
The usual way is a <label> whose for attribute is the id you passed:
<label for="dog-breed-selector">Breed</label>
<.combobox
id="dog-breed-selector"
name="breed"
list_label="Dog breeds"
options={@breeds}
/>If the name is already on the page, for example in the form of a heading,
you can set aria-labelledby to the ID of that element instead:
<h2 id="breed-heading">Breed</h2>
<.combobox
id="dog-breed-selector"
name="breed"
list_label="Dog breeds"
aria-labelledby="breed-heading"
options={@breeds}
/>aria-labelledby and aria-label are global attributes that are set on
the text input, which is the element that needs the label.
By contrast, list_label labels the listbox and the button that opens it.
It describes the list of options, not the field.
In a form
To use the component in a form, you need to pass the id, name, and
value, and add the label, description, and errors.
<.form for={@form} phx-change="validate" phx-submit="save">
<label for="dog-breed-selector">Breed</label>
<.combobox
id="dog-breed-selector"
name={@form[:breed].name}
value={@form[:breed].value}
list_label="Dog breeds"
options={@breeds}
/>
</.form>The component renders a hidden input with the given name. Its value is the
selected option value, or, if a free text entry is selected, the entered
value.
The text input the user types uses the given name with a _search suffix.
The submitted value is either the label of the selected option or
the current search term. You can use this value to filter options on the
server side, or otherwise ignore it.
With a clear button
If the clearable attribute is set, a clear button is rendered that
unselects the current selection and clears the search term.
<.combobox
id="dog-breed-selector"
name="breed"
list_label="Dog breeds"
clearable
clear_label="Clear breed"
options={@breeds}
/>Both the toggle button and the clear button have default content that can
be overridden with the :toggle and :clear slots.
<.combobox id="dog-breed-selector" name="breed" list_label="Dog breeds" clearable options={@breeds}>
<:clear><Heroicon.x_mark /></:clear>
<:toggle><Heroicon.chevron_down /></:toggle>
</.combobox>With free text
If the free_text attribute is set, the user can choose to submit an
entered value that is not among the options.
<.combobox
id="dog-breed-selector"
name="breed"
list_label="Dog breeds"
free_text
free_text_label="Add breed"
options={@breeds}
/>With options loaded from the server
Set on_search to filter on the server. If set, the hook stops filtering on
the client side, and your handler receives the typed text as the *_search
parameter described above.
<.combobox
id="dog-breed-selector"
name="breed"
list_label="Dog breeds"
options={@breeds}
on_search="search-breeds"
/>def handle_event("search-breeds", %{"breed_search" => term}, socket) do
{:noreply, assign(socket, breeds: Dogs.search_breeds(term))}
endInstead of an event name, you can also pass a Phoenix.LiveView.JS command.
Keyboard
Down- open the listbox, or move to the next option. Opening moves to the selected option, or to the first one if nothing is selected.Up- open the listbox at the last option, or move to the previous one.Alt+Down- open the listbox without moving to an option.Alt+Up- close the listbox, leaving the text as it is.Enter- select the active option.Escape- close the listbox and put the display value of the selection back in the input. With the listbox already closed and the display value unchanged, clear the selection.
The combobox is a single tab stop. Focus stays on the text input and never
moves into the listbox. The active option is tracked with
aria-activedescendant. The toggle and the clear button are out of the tab
order; keyboard users can use Alt + Down and Alt + Up for the
toggle, and Escape for clearing.
Home, End, Left, Right, Backspace and Delete are not intercepted
and are reserved for the browser's text editing.
Example CSS
For example CSS, you can have a look at the demo styles.
Renders a modal dialog for content such as forms and informational panels.
This component is appropriate for non-critical interactions. For dialogs
requiring immediate user response, such as confirmations or warnings, use
.alert_dialog/1 instead.
Maturity: Developing
Configuration
Generate the component with default options:
build_modal()The build macro supports the common options
name, base_class, and modifiers.
Default options
[name: :modal, base_class: "modal", modifiers: []]Usage
The dialog is opened with showModal() in one of three ways: from the URL,
with the show_modal/1 and hide_modal/1 functions, or with a button that
uses the Invoker Commands API.
With URL
To toggle the modal visibility based on the URL:
- Use the
:ifattribute to conditionally render the modal when a specific live action matches. - Set the
on_cancelattribute to patch back to the original URL when the user chooses to close the modal. - Set the
openattribute to declare the modal's initial visibility state.
Example
<.modal
:if={@live_action == :show}
id="pet-modal"
on_cancel={JS.patch(~p"/pets")}
open
>
<:title>Show pet</:title>
<p>My pet is called Johnny.</p>
<:footer>
<.link phx-click={JS.exec("data-cancel", to: "#pet-modal")}>
Close
</.link>
</:footer>
</.modal>To open the modal, patch or navigate to the URL associated with the live action.
<.link patch={~p"/pets/#{@id}"}>show</.link>With JS commands
To toggle the modal visibility dynamically:
- Omit the
openattribute in the template. - Use the
show_modal/1andhide_modal/1functions to change the visibility.
Example
<.modal id="pet-modal">
<:title>Show pet</:title>
<p>My pet is called Johnny.</p>
<:footer>
<.link phx-click={JS.exec("data-cancel", to: "#pet-modal")}>
Close
</.link>
</:footer>
</.modal>To open the modal, use the show_modal/1 function.
<.button
phx-click={Doggo.show_modal("pet-modal")}
aria-haspopup="dialog"
>
show
</.button>With HTML attributes
command and commandfor are the Invoker Commands API. Unlike the other
two ways, this API needs no JavaScript at all.
<.button command="show-modal" commandfor="pet-modal">show</.button>Both attributes are recent, so the hook handles them if the browser doesn't support them.
Closing
Four things close the dialog, and all of them run on_cancel:
- the close button the component renders, which uses
command="close" Escand a click outside, unlessdismissableis set tofalsehide_modal/1JS.exec("data-cancel", to: "#pet-modal")
Semantics
The dialog is opened with showModal(), so the browser puts it in the top
layer, draws ::backdrop, makes the rest of the document inert and keeps
the focus inside. aria-modal is not rendered, because showModal()
already marks the component as a modal.
CSS
A dialog is hidden until it is opened, so no rule is needed for that. Style
the backdrop with dialog.modal::backdrop.
Caveats
Setting dismissable={false} removes the close button and renders
closedby="none", which leaves no way to dismiss the dialog from the
component. Provide your own control in the :footer slot when you do that.
Keyboard
Esc- close the dialog, unlessdismissableis set tofalse.
Opening the dialog moves the focus to the first focusable element inside it, and closing it returns the focus to the element that opened it. The focus stays within the dialog while it is open. A dialog with nothing focusable in it leaves the focus outside.
Example CSS
For example CSS, you can have a look at the demo styles.
Renders a group of radio buttons, for example for a toolbar.
To render radio buttons within a regular form, use input/1 with the
"radio-group" type instead.
Maturity: Developing
Configuration
Generate the component with default options:
build_radio_group()The build macro supports the common options
name, base_class, and modifiers.
Default options
[name: :radio_group, base_class: "radio-group", modifiers: []]Usage
<.radio_group
id="favorite-dog"
name="favorite-dog"
label="Favorite Dog"
options={[
{"Labrador Retriever", "labrador"},
{"German Shepherd", "german_shepherd"},
{"Golden Retriever", "golden_retriever"},
{"French Bulldog", "french_bulldog"},
{"Beagle", "beagle"}
]}
/>CSS
To target the wrapper, you can use an attribute selector:
[role="radio-group"] {}Keyboard
Left,Right,UpandDown- move between the radios and check the one the focus lands on.Space- check the focused radio.
The group is a single tab stop. The radios are native elements sharing a name, so the browser handles this.
Renders a container for a set of controls.
Maturity: Developing
Configuration
Generate the component with default options:
build_toolbar()The build macro supports the common options
name, base_class, and modifiers.
Default options
[name: :toolbar, base_class: "toolbar", modifiers: []]Usage
Direct children of this component can be any types buttons or groups of buttons.
<.toolbar label="Actions for the dog">
<div role="group">
<button phx-click="feed-dog">
<.icon text="Feed dog"><Icons.feed /></.icon>
</button>
<button phx-click="walk-dog">
<.icon text="Walk dog"><Icons.walk /></.icon>
</button>
</div>
<div role="group">
<button phx-click="teach-trick">
<.icon text="Teach a Trick"><Icons.teach /></.icon>
</button>
<button phx-click="groom-dog">
<.icon text="Groom dog"><Icons.groom /></.icon>
</button>
</div>
</.toolbar>This component needs the Doggo.Toolbar JavaScript hook. See
Phoenix LiveView Hooks for
registering it.
Keyboard
LeftandRight- move between the controls, wrapping at the ends. A toolbar withorientation="vertical"usesUpandDowninstead.HomeandEnd- first and last control.
The toolbar is a single tab stop. Tab moves to the control the user last
used, and the arrow keys move between them.
Example CSS
For example CSS, you can have a look at the demo styles.
Renders content with a tooltip.
There are different ways to render a tooltip. This component renders a <div>
with the tooltip role, which is hidden unless the element is hovered on or
focused. For example CSS for this kind of tooltip, refer to
ARIA: tooltip role.
A simpler alternative for styled text-only tooltips is to use a data attribute
and the attr CSS function.
Doggo does not provide a component for that kind of tooltip, since it is
controlled by attributes only. You can check
Pico CSS for an example implementation.
Maturity: Developing
The markup may change. A rewrite on top of the Popover API is being
considered, which would put the tooltip in the top layer, so that an
ancestor with overflow: hidden can no longer clip it. That would change
the elements this component emits and the attributes your stylesheet
targets.
Configuration
Generate the component with default options:
build_tooltip()The build macro supports the common options
name, base_class, and modifiers.
Default options
[name: :tooltip, base_class: "tooltip-container", modifiers: []]Usage
With an inline text:
<p>
Did you know that the
<.tooltip id="labrador-info">
Labrador Retriever
<:tooltip>
<p><strong>Labrador Retriever</strong></p>
<p>
Labradors are known for their friendly nature and excellent
swimming abilities.
</p>
</:tooltip>
</.tooltip>
is one of the most popular dog breeds in the world?
</p>If the inner block contains a link or another focusable element, add the
contains_link attribute and put aria-describedby on that element. Its
value is the id of the component with -tooltip appended:
<p>
Did you know that the
<.tooltip id="labrador-info" contains_link>
<.link navigate={~p"/labradors"} aria-describedby="labrador-info-tooltip">
Labrador Retriever
</.link>
<:tooltip>
<p><strong>Labrador Retriever</strong></p>
<p>
Labradors are known for their friendly nature and excellent
swimming abilities.
</p>
</:tooltip>
</.tooltip>
is one of the most popular dog breeds in the world?
</p>This component needs the Doggo.Tooltip JavaScript hook for Esc to
dismiss the tooltip. See
Phoenix LiveView Hooks for
registering it.
Your stylesheet decides when the tooltip is visible. Show it on :hover and
:focus-within, and hide it when the root element has the data-dismissed
attribute, which the hook sets. See the example CSS.
Keyboard
Tab- focus the described element, which shows the tooltip.Esc- hide the tooltip while it is shown, without moving the focus.
Example CSS
For example CSS, you can have a look at the demo styles.