Doggo.Components (Doggo v0.18.4)

Copy Markdown View Source

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_relative_time()
  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_toggletip()
  build_toolbar()
  build_tooltip()
  build_tree()
  build_tree_item()
  build_vertical_nav()
  build_vertical_nav_nested()
  build_vertical_nav_section()
end

Common 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 to Phoenix.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 :type option defaults to :string.
  • defaults - Defaults for the optional attributes and slots of the component. See Defaults.

Some components have additional options that are mostly used to allow the customization of certain class names.

The options are evaluated in the module body, so you can pass module attributes and function calls:

@sizes ["small", "normal", "large"]

build_button(modifiers: [size: [values: @sizes, default: "normal"]])

Functions have to be remote captures such as &MyAppWeb.Inputs.ranked/1. Anonymous functions cannot be compiled into the component.

Defaults

Set the defaults of attributes and slots once for every call of the component. An attribute or slot passed at the call overrides the default.

build_icon(defaults: [label_position: "after"])

build_fallback(defaults: [placeholder: gettext("n/a")])

build_alert(defaults: [icon: ~H|<.icon name="info" />|])

A literal, such as label_position: "after", or a remote capture becomes the default of the attribute and is listed in the docs of the component. Any other expression is evaluated each time the component renders without the attribute, so gettext("n/a") returns the text for the current locale. An expression cannot read the assigns of the component. If the attribute is not passed, the default is used. If it is passed as nil, its value is nil.

Only defaults written in the build call are evaluated at render time. Defaults from a module attribute, as in build_alert(defaults: @defaults), are evaluated when the module compiles, so a gettext call there is translated once, into the locale at compile time.

A slot default is a remote capture of a function component or inline HEEx. It renders when the slot is empty, with the values of the modifiers of the component as assigns, such as @level for alert. For a slot with attributes, pass them in a keyword list with the content as inner_block:

build_carousel(
  defaults: [
    previous: [
      label: gettext("Previous slide"),
      inner_block: ~H|<.icon name="chevron-left" />|
    ]
  ]
)

A default label for a landmark, such as breadcrumb, names every instance the same. If a page has two, pass a label to each.

The default of a modifier is set in modifiers:.

Summary

Buttons

The action bar offers users quick access to primary actions within the application.

Renders a button.

Renders a link (<a>) that has the style of a button.

Renders a button that toggles the visibility of another element.

Renders a switch as a button.

Renders a button that toggles a state.

Renders a container for a set of controls.

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.

Renders a value or a fallback value if the value is empty.

Renders a list of properties as key/value pairs.

Renders a DateTime or Date relative to now, such as "3 minutes ago" or "yesterday", in a <time> element.

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.

Dialog

Renders an alert dialog that requires the immediate attention and response of the user.

Renders a modal dialog for content such as forms and informational panels.

Feedback

The alert component serves as a notification mechanism to provide feedback to the user.

Renders a badge, typically used for drawing attention to elements like notification counts.

Renders supplementary information related to the main content.

Renders a skeleton loader, a placeholder for content that is in the process of loading.

Renders a button that reveals supplementary information when it is pressed.

Renders content with a tooltip.

Form

Renders a text input with a popup that allows users to select a value from a list of suggestions.

Renders a form field including input, label, errors, and description.

Groups inputs in a form visually.

Renders a group of radio buttons, for example for a toolbar.

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.

Groups child elements with a consistent gap between them.

Renders a drawer with a header, body, and footer 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 an optional label.

Renders an icon using an SVG sprite.

Renders an image with an optional caption.

Menu

Renders a menu that offers a list of actions or functions.

Renders a menu bar, similar to those found in desktop applications.

Renders a button that toggles an actions menu.

Groups items within a menu/1 or menu_bar/1.

Renders a button that acts as a menu item within a menu/1 or menu_bar/1.

Renders a menu item checkbox as part of a menu/1 or menu_bar/1.

Renders a group of menu item radios as part of a menu/1 or menu_bar/1.

Navigation

Renders a navigation that sticks to the bottom of the screen.

Renders a breadcrumb navigation.

Renders a navigation bar.

Renders a list of navigation items.

Renders a navigation for form steps.

Renders navigation tabs.

Renders a vertical navigation menu.

Renders nested navigation items within the :item slot of the vertical_nav/1 component.

Renders a section within a sidebar or drawer that contains one or more items which are not navigation links.

Buttons

build_action_bar(opts \\ [])

(since 0.6.0) (macro)

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, modifiers, and defaults.

Default options

[name: :action_bar, base_class: "action-bar", modifiers: []]

Usage

<.action_bar id="dog-actions" label="Dog actions">
  <: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

  • Left and Right - move between the buttons, wrapping at the ends.
  • Home and End - first and last button.
  • Enter or Space - run the focused button's on_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.

build_button(opts \\ [])

(since 0.6.0) (macro)

Renders a button.

Use this component when you need to perform an action such as submitting a form, confirming an action, deleting an item, toggling a setting, etc.

If you need to navigate to a different page or a specific section on the current page and want to style the link like a button, use button_link/1 instead.

See also button_link/1, toggle_button/1, and disclosure_button/1.

Maturity: Stable

Configuration

Generate the component with default options:

build_button()

The build macro supports the common options name, base_class, modifiers, and defaults.

Default options

[
  name: :button,
  base_class: "button",
  modifiers: [
    variant: [
      values: ["primary", "secondary", "info", "success", "warning", "danger"],
      default: "primary"
    ],
    size: [values: ["small", "normal", "medium", "large"], default: "normal"],
    fill: [values: ["solid", "outline", "text"], default: "solid"],
    shape: [values: [nil, "circle", "pill"], default: nil]
  ]
]

Usage

<.button>Confirm</.button>

<.button type="submit">
  Submit
</.button>

To indicate a loading state, for example when submitting a form, use the aria-busy attribute:

<.button aria-label="Saving..." aria-busy>
  click me
</.button>

Example CSS

For example CSS, you can have a look at the demo styles.

build_button_link(opts \\ [])

(since 0.6.0) (macro)

Renders a link (<a>) that has the style of a button.

Use this component when you need to style a link to a different page or a specific section within the same page as a button.

To perform an action on the same page, including toggles and revealing/hiding elements, you should always use a real button instead. See button/1, toggle_button/1, and disclosure_button/1.

Maturity: Stable

Configuration

Generate the component with default options:

build_button_link()

The build macro supports the common options name, base_class, modifiers, and defaults.

Default options

[
  name: :button_link,
  base_class: "button",
  modifiers: [
    variant: [
      values: ["primary", "secondary", "info", "success", "warning", "danger"],
      default: "primary"
    ],
    size: [values: ["small", "normal", "medium", "large"], default: "normal"],
    fill: [values: ["solid", "outline", "text"], default: "solid"],
    shape: [values: [nil, "circle", "pill"], default: nil]
  ]
]

Usage

<.button_link patch={~p"/confirm"}>
  Confirm
</.button_link>

<.button_link navigate={~p"/registration"}>
  Registration
</.button_link>

Example CSS

For example CSS, you can have a look at the demo styles.

build_disclosure_button(opts \\ [])

(since 0.6.0) (macro)

Renders a button that toggles the visibility of another element.

Use this component to reveal or hide additional content, such as in collapsible sections or dropdown menus.

For a button that toggles other states, use toggle_button/1 instead. See also button/1 and button_link/1.

Maturity: Refining

Configuration

Generate the component with default options:

build_disclosure_button()

The build macro supports the common options name, base_class, modifiers, and defaults.

Default options

[
  name: :disclosure_button,
  base_class: "button",
  modifiers: [
    variant: [
      values: ["primary", "secondary", "info", "success", "warning", "danger"],
      default: "primary"
    ]
  ]
]

Usage

Set the controls attribute to the DOM ID of the element that you want to toggle with the button.

The initial state is hidden. Do not forget to add the hidden attribute to the toggled element. Otherwise, visibility of the element will not align with the aria-expanded attribute of the button.

<.disclosure_button controls="data-table">
  Data Table
</.disclosure_button>

<table id="data-table" hidden></table>

CSS

To select disclosure buttons without class names and to apply styles depending on the state (e.g. to render a caret), you can use the aria-controls and aria-expanded attributes.

button[aria-controls][aria-expanded] {
  /* Base styles for disclosure buttons */
}

button[aria-controls][aria-expanded="true"] {
  /* Styles when content is visible */
}

Keyboard

  • Enter or Space - show or hide the controlled element.

Example CSS

For example CSS, you can have a look at the demo styles.

build_switch(opts \\ [])

(since 0.6.0) (macro)

Renders a switch as a button.

If you want to render a switch as part of a form, use the input/1 component with the type "switch" instead.

Note that this component only renders a button with a label, a state, and <span> with the class switch-control. You will need to style the switch control span with CSS in order to give it the appearance of a switch.

Maturity: Developing

Configuration

Generate the component with default options:

build_switch()

The build macro supports the common options name, base_class, modifiers, and defaults.

Default options

[name: :switch, base_class: "switch", modifiers: []]

Usage

<.switch
  label="Subscribe"
  checked={true}
  phx-click="toggle-subscription"
/>

Example CSS

For example CSS, you can have a look at the demo styles.

build_toggle_button(opts \\ [])

(since 0.6.0) (macro)

Renders a button that toggles a state.

Use this component to switch a feature or setting on or off, for example to toggle dark mode or mute/unmute sound.

See also button/1, button_link/1, and disclosure_button/1.

Maturity: Developing

Configuration

Generate the component with default options:

build_toggle_button()

The build macro supports the common options name, base_class, modifiers, and defaults.

Default options

[
  name: :toggle_button,
  base_class: "button",
  modifiers: [
    variant: [
      values: ["primary", "secondary", "info", "success", "warning", "danger"],
      default: "primary"
    ],
    size: [values: ["small", "normal", "medium", "large"], default: "normal"],
    fill: [values: ["solid", "outline", "text"], default: "solid"],
    shape: [values: [nil, "circle", "pill"], default: nil]
  ]
]

Usage

With a Phoenix.LiveView.JS command:

<.toggle_button on_click={JS.push("toggle-mute")} pressed={@muted}>
  Mute
</.toggle_button>

Accessibility

The button state is conveyed via the aria-pressed attribute and the button styling. The button text should not change depending on the state. You may however include an icon that changes depending on the state.

CSS

A toggle button can be identified with an attribute selector for the aria-pressed attribute.

// any toggle button regardless of state
button[aria-pressed] {}

// unpressed toggle buttons
button[aria-pressed="false"] {}

// pressed toggle buttons
button[aria-pressed="true"] {}

Keyboard

  • Enter or Space - toggle aria-pressed and run on_click.

Example CSS

For example CSS, you can have a look at the demo styles.

build_toolbar(opts \\ [])

(since 0.6.0) (macro)

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, modifiers, and defaults.

Default options

[name: :toolbar, base_class: "toolbar", modifiers: []]

Usage

Direct children of this component can be any types buttons or groups of buttons.

<.toolbar id="dog-toolbar" label="Actions for the dog">
  <div role="group">
    <button phx-click="feed-dog">
      <.icon label="Feed dog"><Icons.feed /></.icon>
    </button>
    <button phx-click="walk-dog">
      <.icon label="Walk dog"><Icons.walk /></.icon>
    </button>
  </div>
  <div role="group">
    <button phx-click="teach-trick">
      <.icon label="Teach a Trick"><Icons.teach /></.icon>
    </button>
    <button phx-click="groom-dog">
      <.icon label="Groom dog"><Icons.groom /></.icon>
    </button>
  </div>
</.toolbar>

This component needs the Doggo.Toolbar JavaScript hook. See Phoenix LiveView Hooks for registering it.

Keyboard

  • Left and Right - move between the controls, wrapping at the ends. A toolbar with orientation="vertical" uses Up and Down instead.
  • Home and End - 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.

Data

build_accordion(opts \\ [])

(since 0.6.0) (macro)

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, modifiers, and defaults.

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

  • Enter or Space - toggle the section of the focused header.
  • Up and Down - move to the previous or the next header, wrapping at the ends.
  • Home and End - 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.

Example CSS

For example CSS, you can have a look at the demo styles.

build_card(opts \\ [])

(since 0.6.0) (macro)

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, modifiers, and defaults.

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>

Example CSS

For example CSS, you can have a look at the demo styles.

build_date(opts \\ [])

(since 0.6.0) (macro)

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. As measured in October 2026, Orca reads ISO 8601 text such as 2023-02-05 as separate numbers and dashes and 08:23 as separate numbers and colons. VoiceOver reads ISO 8601 dates, but not dates with slashes such as 2/5/23. NVDA, JAWS and TalkBack have not been tested.

Configuration

Generate the component with default options:

build_date()

The build macro supports the common options name, base_class, modifiers, and defaults.

Default options

[name: :date, base_class: "date", modifiers: []]

Usage

By default, the given value is formatted for display in the ISO 8601 format. This:

<.date value={~D[2023-02-05]} />

Will be rendered as:

<time class="date" 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 class="date" datetime="2023-02-05">
  Feb 2, 2023
</time>

Set precision to show fewer units of the date, for example only the year of a birthdate. The option is applied to the datetime attribute and the default formatter. The formatter and title_formatter functions receive the full date.

<.date value={~D[1980-05-17]} precision={:year} />

Which would be rendered as:

<time class="date" datetime="1980">
  1980
</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. Touch devices do not show the title.

<.date
  value={@date}
  formatter={&relative_date/1}
  title_formatter={&MyApp.Cldr.Date.to_string!/1}
/>

VoiceOver and Orca do not announce the datetime attribute. VoiceOver on iOS and Orca do not announce the title either. VoiceOver on macOS announces an element with a title as a group named by the title, then reads the text and the title again. Make sure that the text is understandable on its own.

Orca reads the default ISO 8601 text as separate numbers and dashes. VoiceOver reads it as a date, but not a date with slashes such as 2/5/23, which localize={:short} writes in some locales. To write the month as a word, set localize to :long, or pass a formatter that uses the locale data of your application. Only a formatter changes the text for users without JavaScript.

Finally, the component can shift a DateTime to a different time zone before converting it to a date. The datetime attribute contains the full value, unless precision is set:

<.date
  value={~U[2023-02-05 23:22:05Z]}
  timezone="Asia/Tokyo"
/>

Which would be rendered as:

<time class="date" datetime="2023-02-06T08:22:05+09:00">
  2023-02-06
</time>

build_datetime(opts \\ [])

(since 0.6.0) (macro)

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. As measured in October 2026, Orca reads ISO 8601 text such as 2023-02-05 as separate numbers and dashes and 08:23 as separate numbers and colons. VoiceOver reads ISO 8601 dates, but not dates with slashes such as 2/5/23. NVDA, JAWS and TalkBack have not been tested.

Configuration

Generate the component with default options:

build_datetime()

The build macro supports the common options name, base_class, modifiers, and defaults.

Default options

[name: :datetime, base_class: "datetime", 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 class="datetime" 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 class="datetime" 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. Touch devices do not show the title.

<.datetime
  value={@datetime}
  formatter={&relative_date/1}
  title_formatter={&MyApp.Cldr.DateTime.to_string!/1}
/>

VoiceOver and Orca do not announce the datetime attribute. VoiceOver on iOS and Orca do not announce the title either. VoiceOver on macOS announces an element with a title as a group named by the title, then reads the text and the title again. Make sure that the text is understandable on its own.

Orca reads the default ISO 8601 text as separate numbers and dashes. VoiceOver reads it as a date, but not a date with slashes such as 2/5/23, which localize={:short} writes in some locales. To write the month as a word, set localize to :long, or pass a formatter that uses the locale data of your application. Only a formatter changes the text for users without JavaScript. A long format takes more space, for example in a table column.

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 class="datetime" datetime="2023-02-06T08:22:05+09:00">
  2023-02-06 08:22:05+09:00 JST Asia/Tokyo
</time>

build_fallback(opts \\ [])

(since 0.6.0) (macro)

Renders a value or a fallback value if the value is empty.

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, modifiers, and defaults.

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"
/>

Example CSS

For example CSS, you can have a look at the demo styles.

build_property_list(opts \\ [])

(since 0.6.0) (macro)

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, modifiers, and defaults.

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.

build_relative_time(opts \\ [])

(since 0.18.0) (macro)

Renders a DateTime or Date relative to now, such as "3 minutes ago" or "yesterday", in a <time> element.

Maturity: Developing

Configuration

Generate the component with default options:

build_relative_time()

The build macro supports the common options name, base_class, modifiers, and defaults.

Default options

[name: :relative_time, base_class: "relative-time", modifiers: []]

Usage

The server renders the absolute value. The browser replaces it with the relative text in the page's language, which requires calling relativeTimes() from @woylie/doggo in your app.js. Without JavaScript, the absolute value remains.

<.relative_time value={@post.inserted_at} />

The text is converted once. Set sync to keep it up to date while the page is open:

<.relative_time value={@message.sent_at} sync />

Past a threshold, the absolute value is shown instead. With localize, the absolute value is in the user's format:

<.relative_time
  value={@event.starts_at}
  threshold={Duration.new!(week: 1)}
  localize={:medium}
/>

The title contains the absolute value.

A Date is compared by calendar day, so the text reads "yesterday" or "in 2 days".

To stop the relative times inside an element from updating, for example as a user setting, set data-relative-sync="false" on that element. It overrides sync.

VoiceOver on macOS announces the element as a group named by the title, then reads the text and the title again. Without JavaScript, past the threshold and when printing, the text is the absolute value. Orca reads the default ISO 8601 text as separate numbers and dashes. VoiceOver reads it as a date, but not a date with slashes such as 2/5/23, which localize={:short} writes in some locales. To write the month as a word, set localize to :long, or pass a formatter that uses the locale data of your application. Only a formatter changes the text for users without JavaScript.

build_table(opts \\ [])

(since 0.6.0) (macro)

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, modifiers, and defaults.

Default options

[name: :table, base_class: "table-container", modifiers: []]

Usage

<.table id="pets" rows={@pets} caption="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}
  caption="Pets"
  row_click={&JS.navigate(~p"/pets/#{&1}")}
>
  <:col :let={p} label="name">
    <.link navigate={~p"/pets/#{p}"}><%= p.name %></.link>
  </:col>
</.table>

Scroll container

The div around the table has tabindex="0", so that a table that is wider than its container can be scrolled using the keyboard. This assumes that you make the container scrollable:

.table-container-container {
  overflow-x: auto;
}

Set caption or label to name the container, which is then exposed as a region. A blank value counts as unset.

If the table fits its container, or sits in another scroll container, set scrollable={false}. The wrapper then has no tabindex, no role and no name, and does not add a useless tab stop.

Example CSS

For example CSS, you can have a look at the demo styles.

build_tabs(opts \\ [])

(since 0.6.0) (macro)

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, modifiers, and defaults.

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, Up and Down - previous or next tab, wrapping at the ends. A tab list with orientation="horizontal" uses Left and Right, one with orientation="vertical" uses Up and Down.
  • Home and End - first and last tab.
  • Enter or Space - select the focused tab.

The tab list is a single tab stop. The arrow keys, Home and End move focus to a disabled tab but do not select it.

Example CSS

For example CSS, you can have a look at the demo styles.

build_tag(opts \\ [])

(since 0.6.0) (macro)

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, modifiers, and defaults.

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.

build_time(opts \\ [])

(since 0.6.0) (macro)

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. As measured in October 2026, Orca reads ISO 8601 text such as 2023-02-05 as separate numbers and dashes and 08:23 as separate numbers and colons. VoiceOver reads ISO 8601 dates, but not dates with slashes such as 2/5/23. NVDA, JAWS and TalkBack have not been tested.

Configuration

Generate the component with default options:

build_time()

The build macro supports the common options name, base_class, modifiers, and defaults.

Default options

[name: :time, base_class: "time", modifiers: []]

Usage

By default, the time part of the given value is formatted for display with to_string/1. For a DateTime or NaiveDateTime, the datetime attribute contains the full value. This:

<.time value={~T[12:22:06.003]} />

Will be rendered as:

<time class="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 class="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. Touch devices do not show the title.

<.time
  value={@time}
  formatter={&relative_time/1}
  title_formatter={&MyApp.Cldr.Time.to_string!/1}
/>

VoiceOver and Orca do not announce the datetime attribute. VoiceOver on iOS and Orca do not announce the title either. VoiceOver on macOS announces an element with a title as a group named by the title, then reads the text and the title again. Make sure that the text is understandable on its own.

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 class="time" datetime="2023-02-06T08:22:05+09:00">
  08:22:05
</time>

build_tree(opts \\ [])

(since 0.6.0) (macro)

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-selected from the selected attribute 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, modifiers, and defaults.

Default options

[name: :tree, base_class: "tree", modifiers: []]

Usage

<.tree id="dog-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>

From data

A tree_item with an :items slot is a branch, even if the slot is empty. If you render the tree from data, conditionally hide the slot with an :if attribute.

<.tree id="breed-tree" label="Breeds">
  <.breed_node :for={node <- @nodes} node={node} />
</.tree>
defp breed_node(assigns) do
  ~H"""
  <.tree_item>
    {@node.name}
    <:items :if={@node.children != []}>
      <.breed_node :for={child <- @node.children} node={child} />
    </:items>
  </.tree_item>
  """
end

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

  • Down and Up - 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.
  • Home and End - 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.

build_tree_item(opts \\ [])

(since 0.6.0) (macro)

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-selected from the selected attribute 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, modifiers, and defaults.

Default options

[name: :tree_item, base_class: "tree-item", modifiers: []]

Usage

<.tree id="dog-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.

Dialog

build_alert_dialog(opts \\ [])

(since 0.6.0) (macro)

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()

In addition to the common options name, base_class, modifiers, and defaults, the build macro also supports the following options.

  • :close_label - The accessible name of the close button, and its text when :close is not set. Defaults to "Close". An expression, such as gettext("Close") or a call to any other function, is evaluated at render time.
  • :close - The content of the close button: a remote capture of a function component or inline HEEx, such as ~H|<.icon name="x" />|. It renders with the values of the modifiers as assigns. Defaults to nil, which renders close_label as text.

Default options

[
  name: :alert_dialog,
  base_class: "alert-dialog",
  modifiers: [],
  close_label: "Close",
  close: nil
]

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={Doggo.JS.hide_modal("end-session-modal")}>
      No, continue training
    </.button>
  </:footer>
</.alert_dialog>

To open the dialog, use the Doggo.JS.show_modal/1 function.

<.button
  phx-click={Doggo.JS.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 Doggo.JS.hide_modal/1,
  • using the close button or Esc, unless closedby is "none", or
  • clicking outside it, if closedby is "any".

Each of them runs on_cancel once.

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.

Focus

showModal() moves the focus into the dialog to the first element with the autofocus attribute, or the first focusable element if no element has it.

An alert dialog renders no close button by default, so the first focusable element is usually the first control in the :footer slot. With a close button, it is the close button. Neither is likely to be the right element to focus.

Set autofocus on the element that should receive the focus:

<:footer>
  <.button phx-click="end-session">Yes, end session</.button>
  <.button
    autofocus
    phx-click={Doggo.JS.hide_modal("end-session-modal")}
  >
    No, continue training
  </.button>
</:footer>

In an alert dialog, the focus should move to the least destructive action, as recommended in the ARIA Authoring Practices.

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 defaults to closedby="none", which means that it does not render a close button. Provide your own control in the :footer slot.

Keyboard

  • Esc - close the dialog, unless closedby is "none".

Example CSS

For example CSS, you can have a look at the demo styles.

build_modal(opts \\ [])

(since 0.6.0) (macro)

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()

In addition to the common options name, base_class, modifiers, and defaults, the build macro also supports the following options.

  • :close_label - The accessible name of the close button, and its text when :close is not set. Defaults to "Close". An expression, such as gettext("Close") or a call to any other function, is evaluated at render time.
  • :close - The content of the close button: a remote capture of a function component or inline HEEx, such as ~H|<.icon name="x" />|. It renders with the values of the modifiers as assigns. Defaults to nil, which renders close_label as text.

Default options

[
  name: :modal,
  base_class: "modal",
  modifiers: [],
  close_label: "Close",
  close: nil
]

Usage

The dialog is opened with showModal() in one of three ways: from the URL, with the Doggo.JS.show_modal/1 and Doggo.JS.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:

  1. Use the :if attribute to conditionally render the modal when a specific live action matches.
  2. Set the on_cancel attribute to patch back to the original URL when the user chooses to close the modal.
  3. Set the open attribute 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={Doggo.JS.hide_modal("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:

  1. Omit the open attribute in the template.
  2. Use the Doggo.JS.show_modal/1 and Doggo.JS.hide_modal/1 functions to change the visibility.

Example

<.modal id="pet-modal">
  <:title>Show pet</:title>
  <p>My pet is called Johnny.</p>
  <:footer>
    <.link phx-click={Doggo.JS.hide_modal("pet-modal")}>
      Close
    </.link>
  </:footer>
</.modal>

To open the modal, use the Doggo.JS.show_modal/1 function.

<.button
  phx-click={Doggo.JS.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

These close the dialog, and all of them run on_cancel:

  • the close button the component renders, which uses command="close", and Esc, unless closedby is "none"
  • a click outside, if closedby is "any"
  • Doggo.JS.hide_modal/1

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.

Focus

showModal() moves the focus into the dialog to the first element with the autofocus attribute, or the first focusable element if no element has it.

Unless closedby is "none", the close button comes first in the markup, so it receives the focus if no autofocus attribute is present. This is rarely desired.

Set autofocus on the element that should receive the focus:

<.modal id="edit-dog">
  <:title>Edit dog</:title>
  <form>
    <input type="text" name="name" autofocus />
  </form>
</.modal>

The most appropriate element to focus depends on the dialog:

  • If the reader has to work through the content, focus a static element at the top. Opening a modal announces its title and the focused element, but not the body. If a control is focused, the content remains unread until the reader starts looking for it. Focusing a control also scrolls it into view, which can push the beginning of a long body out of sight.
  • If the dialog has focusable elements in the body, such as a form, set the focus to the first such element (e.g. the first input).
  • If the dialog only informs or continues a process, set the focus to the OK or Continue button.
  • If the dialog completes a step that is not easily reversible, set the focus to the least destructive action.

To focus a static element, set both tabindex="-1" and autofocus:

<.modal id="terms">
  <:title>Terms of service</:title>
  <p tabindex="-1" autofocus>Read the following before continuing.</p>
  <h3>Eligibility</h3>
  ...
</.modal>

If the body is short, it can be announced when the modal opens instead by setting aria-describedby to the id of the content element (modal id plus -content suffix):

<.modal id="delete-dog" aria-describedby="delete-dog-content">
  <:title>Delete Bella?</:title>
  <p>This cannot be undone.</p>
</.modal>

A description is announced as a single run of text. Don't set aria-describedby if the content has a structure to navigate, such as a form, a table, or multiple paragraphs.

See also ARIA Authoring Practices.

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 closedby="none" removes the close button, 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, unless closedby is "none".

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.

Feedback

Form

build_combobox(opts \\ [])

(since 0.6.0) (macro)

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()

In addition to the common options name, base_class, modifiers, and defaults, the build macro also supports the following options.

  • :clear - The content of the clear button. Defaults to nil, which renders a multiplication sign. The accessible name comes from clear_label.
  • :toggle - The content of the button that opens the listbox. Defaults to nil, which renders a downwards-pointing triangle. The accessible name comes from list_label.

Both take a remote capture of a function component or inline HEEx, such as ~H|<.icon name="x" />|.

Default options

[
  name: :combobox,
  base_class: "combobox",
  modifiers: [],
  clear: nil,
  toggle: nil
]

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 replaced with the build options clear and toggle:

build_combobox(
  clear: ~H|<Heroicon.x_mark />|,
  toggle: ~H|<Heroicon.chevron_down />|
)

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))}
end

Instead 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.

build_field(opts \\ [])

(since 0.6.0) (macro)

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, modifiers, and defaults, the build macro also supports the following options.

  • :required_text - The text rendered next to the label of a required field. Defaults to "(required)". An expression is evaluated at render time, so gettext("required"), or a call to any other translation function, returns the text for the current locale. nil renders no mark. If you use a symbol such as an asterisk, add a sentence that explains that fields marked with it are required.
  • :optional_text - The text rendered next to the label of an optional field. Defaults to nil, which renders no mark. Evaluated at render time, same as required_text.
  • :translate_error - A remote capture of a function that receives an error of the form's field, such as {"can't be blank", [validation: :required]}, and returns the message. Without it, the message is rendered with its values interpolated and untranslated. Errors passed in errors are rendered as given.
  • :types - A map from a type name to the control that renders it. A map value can be a function component, a function component marked as a group ({component, group: true}), :default for the built-in control, or nil to remove a built-in type. See Custom types below.

Default options

[
  name: :field,
  base_class: "field",
  modifiers: [],
  gettext_module: nil,
  required_text: "(required)",
  optional_text: nil,
  translate_error: nil,
  types: nil,
  extra_types: nil
]

Usage

Custom types

You can register additional input types at build time with the types option.

build_field(
  types: %{"ranked" => &MyAppWeb.Inputs.ranked/1}
)

The additional types can be rendered like any other types.

<.field field={@form[:rank]} type="ranked" label="Rank" />

The map is merged into the built-in types. You can set an entry to nil to remove a built-in type you do not use, and to :default to keep the built-in control, for example after a shared configuration replaced it.

build_field(
  types: %{
    "select" => &MyAppWeb.Inputs.select/1,
    "color" => nil,
    "week" => nil
  }
)

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(
  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
namethe input name, with [] appended when multiple is set
idthe id attribute, from id or the form field
valuethe field value
typethe type you registered
optionsthe options the caller passed, or nil
promptthe prompt the caller passed, or nil
multiplewhether the field takes more than one value
invalidwhether the field has errors, for aria-invalid
describedbyfor aria-describedby, nil without a description
errormessagefor aria-errormessage, nil without errors
validationsthe validation attributes, without those the caller passed in rest
restthe 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, doc: "Any additional HTML attributes."

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>
  """
end

Types

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. In checkbox-group and radio-group, it is applied to each control. The extra keys of a keyword option override it, except required and autofocus. A checkbox-group does not apply required to its checkboxes, which would require the user to check all of them. autofocus is applied to one control: the first checked one, otherwise the first one that is not disabled.

Translations

Pass the texts as calls to your translation library, and point translate_error at the function that translates your errors. With Gettext:

build_field(
  required_text: gettext("required"),
  optional_text: gettext("optional"),
  translate_error: &MyAppWeb.CoreComponents.translate_error/1
)

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 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, Up and Down - 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.

Example CSS

For example CSS, you can have a look at the demo styles.

build_field_group(opts \\ [])

(since 0.6.0) (macro)

Groups inputs in a form visually.

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, modifiers, and defaults.

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>

Example CSS

For example CSS, you can have a look at the demo styles.

build_radio_group(opts \\ [])

(since 0.6.0) (macro)

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, modifiers, and defaults.

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="radiogroup"] {}

Keyboard

  • Left, Right, Up and Down - 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.

Example CSS

For example CSS, you can have a look at the demo styles.

Layout

build_app_bar(opts \\ [])

(since 0.6.0) (macro)

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, modifiers, and defaults.

Default options

[name: :app_bar, base_class: "app-bar", modifiers: []]

Usage

<.app_bar id="app-bar">
  <:title>Page title</: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>

Example CSS

For example CSS, you can have a look at the demo styles.

build_box(opts \\ [])

(since 0.6.0) (macro)

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, modifiers, and defaults.

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>

A box is not a named landmark by default. To turn it into one, set id and point aria-labelledby at its title:

<.box id="profile" aria-labelledby="profile-title">
  <:title>Profile</:title>
  <p>This is a profile.</p>
</.box>

In this example, the body has the ID profile-body. You pass the ID to a disclosure button in the :action slot to toggle visibility.

Example CSS

For example CSS, you can have a look at the demo styles.

build_cluster(opts \\ [])

(since 0.6.0) (macro)

Groups child elements with 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, modifiers, and defaults.

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.

build_drawer(opts \\ [])

(since 0.6.0) (macro)

Renders a drawer with a header, body, and footer 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, modifiers, and defaults.

Default options

[name: :drawer, base_class: "drawer", modifiers: []]

Usage

Minimal example:

<.drawer id="drawer">
  <:body>Content</:body>
</.drawer>

With all slots:

<.drawer id="drawer">
  <:header>Doggo</:header>
  <:body>Content at the top</:body>
  <:footer>Content at the bottom</:footer>
</.drawer>

With navigation and sections:

<.drawer id="drawer">
  <:header>
    <.link navigate={~p"/"}>App</.link>
  </:header>
  <:body>
    <.vertical_nav id="main-nav" label="Main">
      <:item>
        <.link navigate={~p"/dashboard"}>Dashboard</.link>
      </:item>
      <:item>
        <.vertical_nav_nested id="content-nav">
          <: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 id="search">
      <:title>Search</:title>
      <:item><input type="search" placeholder="Search" /></:item>
    </.vertical_nav_section>
  </:body>
  <:footer>
    <.vertical_nav id="user-nav" label="User menu">
      <:item>
        <.link navigate={~p"/settings"}>Settings</.link>
      </:item>
      <:item>
        <.link navigate={~p"/logout"}>Logout</.link>
      </:item>
    </.vertical_nav>
  </:footer>
</.drawer>

Semantics

With a header, the drawer renders role="complementary", named by the header. Set role to choose another landmark:

  • role="navigation" for a drawer that is the site navigation. The header still names it.
  • role={nil} for a drawer that holds a vertical_nav/1, which is a navigation landmark of its own. The page then has no complementary landmark around it.

Without a header, the drawer is no landmark unless you set role, and you name it with aria-label. With a header, the header names the drawer, and an aria-label has no effect.

Example CSS

For example CSS, you can have a look at the demo styles.

build_page_header(opts \\ [])

(since 0.6.0) (macro)

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, modifiers, and defaults.

Default options

[name: :page_header, base_class: "page-header", modifiers: []]

Usage

<main>
  <.page_header subtitle="Share Your Pup's Story">
    <:title>Puppy Profiles</:title>
    <: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</:title>
    <: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>

Example CSS

For example CSS, you can have a look at the demo styles.

build_split_pane(opts \\ [])

(since 0.6.0) (macro)

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, modifiers, and defaults.

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

  • Left and Right - move a vertical separator by one percent.
  • Up and Down - move a horizontal separator by one percent.
  • Shift + arrow key - move by ten percent.
  • Home and End - 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.

build_stack(opts \\ [])

(since 0.6.0) (macro)

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, modifiers, and defaults.

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

build_avatar(opts \\ [])

(since 0.6.0) (macro)

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, modifiers, and defaults.

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" />

Example CSS

For example CSS, you can have a look at the demo styles.

build_carousel(opts \\ [])

(since 0.6.0) (macro)

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.

The element around the slides has tabindex="0", so that the slides can be scrolled by keyboard and not only stepped through with the controls.

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

The build options carousel_roledescription, slide_roledescription, pagination_label and pagination_slide_label, and the labels of the :pause slot, default to English. They are announced by screen readers and should be translated, as should the labels of the :previous and :next slots.

Defaults

A default for the :pause, :previous or :next slot turns on rotation or the previous and next buttons for every carousel of the build, and a call cannot turn them off. If some carousels need them and others do not, build the carousel a second time under another name: with other defaults, or set no defaults and pass the slots at each call.

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()

In addition to the common options name, base_class, modifiers, and defaults, the build macro also supports the following options.

  • :carousel_roledescription - The aria-roledescription of the carousel. Defaults to "carousel".
  • :slide_roledescription - The aria-roledescription of a slide. Defaults to "slide".
  • :pagination_label - The name of the tablist of slide pickers. Defaults to "Slides".
  • :pagination_slide_label - A function that takes the slide number and returns the name of the pagination tab of a slide without a label. Defaults to &Doggo.slide_label/1.

All four values should be translated to the language in which the rest of the page is displayed. The texts are evaluated at render time, so gettext("carousel"), or a call to any other translation function, returns the text for the current locale. pagination_slide_label can be written as an anonymous function in the build call, such as fn n -> gettext("Slide %{n}", n: n) end.

Default options

[
  name: :carousel,
  base_class: "carousel",
  modifiers: [],
  carousel_roledescription: "carousel",
  slide_roledescription: "slide",
  pagination_label: "Slides",
  pagination_slide_label: &Doggo.slide_label/1
]

Usage

<.carousel id="dog-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

  • Left and Right - previous or next slide, with the focus on a pagination tab. With loop they wrap, without it they stop at the ends.
  • Home and End - 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.

build_frame(opts \\ [])

(since 0.6.0) (macro)

Renders a frame with an aspect ratio for images or videos.

Maturity: Developing

Configuration

Generate the component with default options:

build_frame()

In addition to the common options name, base_class, modifiers, and defaults, the build macro also supports the following options.

  • :ratios - The aspect ratios the ratio attribute accepts, as strings in the format n:d. The first one is the default.

Default options

[
  name: :frame,
  base_class: "frame",
  modifiers: [shape: [values: [nil, "circle"], default: nil]],
  ratios: ["1:1", "3:2", "2:3", "4:3", "3:4", "5:4", "4:5", "16:9", "9:16"]
]

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 shape="circle">
  <img src="image.png" alt="An example image illustrating the usage." />
</.frame>

Example CSS

For example CSS, you can have a look at the demo styles.

build_icon(opts \\ [])

(since 0.6.0) (macro)

Renders an icon with an optional label.

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, modifiers, and defaults, 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 a name attribute (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)
end

The name attribute passed to the generated icon component needs to reference a function component in the configured module.

<.icon name="bug_ant" label="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" label="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 a visually hidden label:

<.icon name="bug_ant" label="report bug" />

To display the label visibly:

<.icon name="bug_ant" label="report bug" label_position="after" />

Or:

<.icon name="bug_ant" label="report bug" label_position="before" />

The label_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.

build_icon_sprite(opts \\ [])

(since 0.6.0) (macro)

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, modifiers, and defaults, the build macro also supports the following options.

  • :sprite_url - URL of the icon sprite. An expression such as ~p"/images/icons.svg" is evaluated at render time, so a digested static path works.

Default options

[
  name: :icon_sprite,
  base_class: "icon",
  modifiers: [],
  sprite_url: "/assets/icons/sprite.svg"
]

Usage

Render an icon with a visually hidden label:

<.icon_sprite name="arrow-left" label="Go back" />

To display the label visibly:

<.icon_sprite name="arrow-left" label="Go back" label_position="after" />

Example CSS

For example CSS, you can have a look at the demo styles.

build_image(opts \\ [])

(since 0.6.0) (macro)

Renders an image with an optional caption.

The component uses the frame component for the image's box. The frame needs to be built before the image.

build_frame()
build_image()

To use a frame build with a different name, set the frame option.

build_frame(name: :media_frame, ratios: ["21:9"])
build_image(frame: &__MODULE__.media_frame/1)

Maturity: Developing

Configuration

Generate the component with default options:

build_image()

In addition to the common options name, base_class, modifiers, and defaults, the build macro also supports the following options.

  • :frame - The build of the frame component that renders the image's box, as a remote capture. Defaults to &__MODULE__.frame/1, the frame built in the same module under its default name.

Default options

[name: :image, base_class: "image", modifiers: [], frame: 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>
    Canine couture, spring collection: the season's boldest silhouettes, worn
    on four legs.
  </:caption>
</.image>

Example CSS

For example CSS, you can have a look at the demo styles.