View Source Doggo.Components (Doggo v0.7.0)

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_fab()
  build_fallback()
  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()
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 with the type :string is added. The options will be passed to Phoenix.Component.attr/3. Most components define a set of default modifiers that can be overridden.
  • class_name_fun - A 2-arity function that takes a modifier attribute name and a value and returns a CSS class name. Defaults to Doggo.modifier_class_name/2.

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

Summary

Buttons

Renders a button.

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

Renders a button that toggles the visibility of another element.

Renders a floating action button.

Renders a switch as a button.

Renders a button that toggles a state.

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.

Renders a Date, DateTime, or NaiveDateTime in a <time> tag.

Renders a DateTime or NaiveDateTime in a <time> tag.

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, i.e. key/value pairs.

Renders a simple table.

Renders tab panels.

Renders a tag, typically used for displaying labels, categories, or keywords.

Renders a Time, DateTime, or NaiveDateTime in a <time> tag.

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.

Use the cluster component to visually group children.

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 a customizable icon using a slot for SVG content.

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.

This component can be used to group 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.

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.

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

Link to this macro

build_button(opts \\ [])

View Source (since 0.6.0) (macro)

Renders a button.

Use this component when you need to perform an action that doesn't involve navigating to a different page, such as submitting a form, confirming an action, or deleting an item.

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: Developing

Generate Component

Generate component with default options:

build_button()

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]
  ],
  class_name_fun: &Doggo.modifier_class_name/2
]

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>
Link to this macro

build_button_link(opts \\ [])

View Source (since 0.6.0) (macro)

Renders a link (<a>) that has the role and 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: Developing

Generate Component

Generate component with default options:

build_button_link()

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]
  ],
  class_name_fun: &Doggo.modifier_class_name/2,
  disabled_class: "is-disabled"
]

Usage

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

<.button_link navigate={~p"/registration"}>
  Registration
</.button_link>
Link to this macro

build_disclosure_button(opts \\ [])

View Source (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: Developing

Generate Component

Generate component with default options:

build_disclosure_button()

Default options

[
  name: :disclosure_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]
  ],
  class_name_fun: &Doggo.modifier_class_name/2
]

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>
Link to this macro

build_fab(opts \\ [])

View Source (since 0.6.0) (macro)

Renders a floating action button.

Maturity: Developing

Generate Component

Generate component with default options:

build_fab()

Default options

[
  name: :fab,
  base_class: "fab",
  modifiers: [
    variant: [
      values: ["primary", "secondary", "info", "success", "warning", "danger"],
      default: "primary"
    ],
    size: [values: ["small", "normal", "medium", "large"], default: "normal"],
    shape: [values: [nil, "circle", "pill"], default: "circle"]
  ],
  class_name_fun: &Doggo.modifier_class_name/2
]

Usage

<.fab label="Add item" phx-click={JS.patch(to: "/items/new")}>
  <.icon><Heroicons.plus /></.icon>
</.fab>
Link to this macro

build_switch(opts \\ [])

View Source (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: Experimental

Generate Component

Generate component with default options:

build_switch()

Default options

[
  name: :switch,
  base_class: "switch",
  modifiers: [],
  class_name_fun: &Doggo.modifier_class_name/2
]

Usage

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

build_toggle_button(opts \\ [])

View Source (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

Generate Component

Generate component with default options:

build_toggle_button()

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]
  ],
  class_name_fun: &Doggo.modifier_class_name/2
]

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

Data

Link to this macro

build_accordion(opts \\ [])

View Source (since 0.6.0) (macro)

Renders a set of headings that control the visibility of their content sections.

Maturity: Developing

Generate Component

Generate component with default options:

build_accordion()

Default options

[
  name: :accordion,
  base_class: "accordion",
  modifiers: [],
  class_name_fun: &Doggo.modifier_class_name/2
]

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>
Link to this macro

build_card(opts \\ [])

View Source (since 0.6.0) (macro)

Renders a card in an article tag, typically used repetitively in a grid or flex box layout.

Maturity: Developing

Generate Component

Generate component with default options:

build_card()

Default options

[
  name: :card,
  base_class: "card",
  modifiers: [],
  class_name_fun: &Doggo.modifier_class_name/2
]

Usage

<.card>
  <:image>
    <img src="image.png" alt="Picture of a dog dressed in a poncho." />
  </:image>
  <:header><h2>Dog Fashion Show</h2></:header>
  <:main>
    The next dog fashion show is coming up quickly. Here's what you need
    to look out for.
  </:main>
  <:footer>
    <span>2023-11-15 12:24</span>
    <span>Events</span>
  </:footer>
</.card>
Link to this macro

build_date(opts \\ [])

View Source (since 0.6.0) (macro)

Renders a Date, DateTime, or NaiveDateTime in a <time> tag.

Maturity: Developing

Generate Component

Generate component with default options:

build_date()

Default options

[
  name: :date,
  base_class: nil,
  modifiers: [],
  class_name_fun: &Doggo.modifier_class_name/2
]

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>
Link to this macro

build_datetime(opts \\ [])

View Source (since 0.6.0) (macro)

Renders a DateTime or NaiveDateTime in a <time> tag.

Maturity: Developing

Generate Component

Generate component with default options:

build_datetime()

Default options

[
  name: :datetime,
  base_class: nil,
  modifiers: [],
  class_name_fun: &Doggo.modifier_class_name/2
]

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>
Link to this macro

build_fallback(opts \\ [])

View Source (since 0.6.0) (macro)

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. In situations where a value in a table column or property list is set to be invisible or not displayed, it's crucial to provide an alternative text for screen readers.

Maturity: Developing

Generate Component

Generate component with default options:

build_fallback()

Default options

[
  name: :fallback,
  base_class: "fallback",
  modifiers: [],
  class_name_fun: &Doggo.modifier_class_name/2
]

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"
/>
Link to this macro

build_property_list(opts \\ [])

View Source (since 0.6.0) (macro)

Renders a list of properties, i.e. key/value pairs.

Maturity: Refining

Generate Component

Generate component with default options:

build_property_list()

Default options

[
  name: :property_list,
  base_class: "property-list",
  modifiers: [],
  class_name_fun: &Doggo.modifier_class_name/2
]

Usage

<.property_list>
  <:prop label={gettext("Name")}>George</:prop>
  <:prop label={gettext("Age")}>42</:prop>
</.property_list>
Link to this macro

build_table(opts \\ [])

View Source (since 0.6.0) (macro)

Renders a simple table.

Maturity: Developing

Generate Component

Generate component with default options:

build_table()

Default options

[
  name: :table,
  base_class: "table-container",
  modifiers: [],
  class_name_fun: &Doggo.modifier_class_name/2
]

Usage

<.table id="pets" rows={@pets}>
  <:col :let={p} label="name"><%= p.name %></:col>
  <:col :let={p} label="age"><%= p.age %></:col>
</.table>
Link to this macro

build_tabs(opts \\ [])

View Source (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: Developing

The necessary JavaScript for making this component fully functional and accessible will be added in a future version.

Missing features

  • Roving tabindex
  • Move focus with arrow keys

Generate Component

Generate component with default options:

build_tabs()

Default options

[
  name: :tabs,
  base_class: "tabs",
  modifiers: [],
  class_name_fun: &Doggo.modifier_class_name/2
]

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>
Link to this macro

build_tag(opts \\ [])

View Source (since 0.6.0) (macro)

Renders a tag, typically used for displaying labels, categories, or keywords.

Maturity: Refining

Generate Component

Generate component with default options:

build_tag()

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]
  ],
  class_name_fun: &Doggo.modifier_class_name/2
]

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>
Link to this macro

build_time(opts \\ [])

View Source (since 0.6.0) (macro)

Renders a Time, DateTime, or NaiveDateTime in a <time> tag.

Maturity: Developing

Generate Component

Generate component with default options:

build_time()

Default options

[
  name: :time,
  base_class: nil,
  modifiers: [],
  class_name_fun: &Doggo.modifier_class_name/2
]

Usage

By default, the given value is formatted for display with to_string/1. This:

<Doggo.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:

<Doggo.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>
Link to this macro

build_tree(opts \\ [])

View Source (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: Experimental

The necessary JavaScript for making this component fully functional and accessible will be added in a future version.

Missing features

  • Expand and collapse nodes
  • Select nodes
  • Navigate tree with arrow keys

Generate Component

Generate component with default options:

build_tree()

Default options

[
  name: :tree,
  base_class: "tree",
  modifiers: [],
  class_name_fun: &Doggo.modifier_class_name/2
]

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"] {}
Link to this macro

build_tree_item(opts \\ [])

View Source (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: Experimental

The necessary JavaScript for making this component fully functional and accessible will be added in a future version.

Missing featumres

  • Expand and collapse nodes
  • Select nodes
  • Navigate tree with arrow keys

Generate Component

Generate component with default options:

build_tree_item()

Default options

[
  name: :tree_item,
  base_class: "tree-item",
  modifiers: [],
  class_name_fun: &Doggo.modifier_class_name/2
]

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>

Feedback

Form

Link to this macro

build_field(opts \\ [])

View Source (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

Generate Component

Generate component with default options:

build_field()

Default options

[
  name: :field,
  base_class: "field",
  modifiers: [],
  class_name_fun: &Doggo.modifier_class_name/2,
  gettext_module: nil,
  addon_left_class: "has-addon-left",
  addon_right_class: "has-addon-right",
  visually_hidden_class: "is-visually-hidden"
]

Usage

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.

Gettext

To translate field errors 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.

Link to this macro

build_field_group(opts \\ [])

View Source (since 0.6.0) (macro)

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

Generate Component

Generate component with default options:

build_field_group()

Default options

[
  name: :field_group,
  base_class: "field-group",
  modifiers: [],
  class_name_fun: &Doggo.modifier_class_name/2
]

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

Link to this macro

build_app_bar(opts \\ [])

View Source (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

Generate Component

Generate component with default options:

build_app_bar()

Default options

[
  name: :app_bar,
  base_class: "app-bar",
  modifiers: [],
  class_name_fun: &Doggo.modifier_class_name/2
]

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>
Link to this macro

build_box(opts \\ [])

View Source (since 0.6.0) (macro)

Renders a box for a section on the page.

Maturity: Developing

Generate Component

Generate component with default options:

build_box()

Default options

[
  name: :box,
  base_class: "box",
  modifiers: [],
  class_name_fun: &Doggo.modifier_class_name/2
]

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>
Link to this macro

build_cluster(opts \\ [])

View Source (since 0.6.0) (macro)

Use the cluster component to visually group children.

Common use cases are groups of buttons, or group of tags.

Maturity: Refining

Generate Component

Generate component with default options:

build_cluster()

Default options

[
  name: :cluster,
  base_class: "cluster",
  modifiers: [],
  class_name_fun: &Doggo.modifier_class_name/2
]

Usage

<.cluster>
  <div>some item</div>
  <div>some other item</div>
</.cluster>
Link to this macro

build_drawer(opts \\ [])

View Source (since 0.6.0) (macro)

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: Experimental

Generate Component

Generate component with default options:

build_drawer()

Default options

[
  name: :drawer,
  base_class: "drawer",
  modifiers: [],
  class_name_fun: &Doggo.modifier_class_name/2
]

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>
Link to this macro

build_page_header(opts \\ [])

View Source (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

Generate Component

Generate component with default options:

build_page_header()

Default options

[
  name: :page_header,
  base_class: "page-header",
  modifiers: [],
  class_name_fun: &Doggo.modifier_class_name/2
]

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>
Link to this macro

build_split_pane(opts \\ [])

View Source (since 0.6.0) (macro)

Renders a horizontal or vertical resizable split pane.

Maturity: Experimental

The necessary JavaScript for making this component fully functional and accessible will be added in a future version.

Missing features

  • Resize panes with the mouse
  • Resize panes with the keyboard

Generate Component

Generate component with default options:

build_split_pane()

Default options

[
  name: :split_pane,
  base_class: "split-pane",
  modifiers: [],
  class_name_fun: &Doggo.modifier_class_name/2
]

Usage

Horizontal separator with label:

<.split_pane
  id="sidebar-splitter"
  label="Sidebar"
  orientation="horizontal"
>
  <:primary>One</:primary>
  <:secondary>Two</:secondary>
</.split_pane>

Horizontal separator with visible label:

<.split_pane id="sidebar-splitter"
  labelledby="sidebar-label"
  orientation="horizontal"
>
  <: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="horizontal"
>
  <:primary>One</:primary>
  <:secondary>
    <.split_pane
      id="filter-splitter"
      label="Filters"
      orientation="vertical"
    >
      <:primary>Two</:primary>
      <:secondary>Three</:secondary>
    </.split_pane>
  </:secondary>
</.split_pane>
Link to this macro

build_stack(opts \\ [])

View Source (since 0.6.0) (macro)

Applies a vertical margin between the child elements.

Maturity: Refining

Generate Component

Generate component with default options:

build_stack()

Default options

[
  name: :stack,
  base_class: "stack",
  modifiers: [],
  class_name_fun: &Doggo.modifier_class_name/2,
  recursive_class: "is-recursive"
]

Usage

<.stack>
  <div>some block</div>
  <div>some other block</div>
</.stack>

To apply a vertical margin on nested elements as well, set recursive to true.

<.stack recursive={true}>
  <div>
    <div>some nested block</div>
    <div>another nested block</div>
  </div>
  <div>some other block</div>
</.stack>

Media

Link to this macro

build_avatar(opts \\ [])

View Source (since 0.6.0) (macro)

Renders profile picture, typically to represent a user.

Maturity: Developing

Generate Component

Generate component with default options:

build_avatar()

Default options

[
  name: :avatar,
  base_class: "avatar",
  modifiers: [
    size: [values: ["small", "normal", "medium", "large"], default: "normal"],
    shape: [values: [nil, "circle"], default: nil]
  ],
  class_name_fun: &Doggo.modifier_class_name/2
]

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" />
Link to this macro

build_carousel(opts \\ [])

View Source (since 0.6.0) (macro)

Renders a carousel for presenting a sequence of items, such as images or text.

Maturity: Experimental

The necessary JavaScript for making this component fully functional and accessible will be added in a future version.

Missing features

  • Handle previous/next buttons
  • Handle pagination tabs
  • Auto rotation
  • Disable auto rotation when controls are used
  • Disable previous/next button on first/last item.
  • Focus management and keyboard support for pagination

Generate Component

Generate component with default options:

build_carousel()

Default options

[
  name: :carousel,
  base_class: "carousel",
  modifiers: [],
  class_name_fun: &Doggo.modifier_class_name/2
]

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/dog_poncho.jpg?raw=true"
      alt="A dog wearing a colorful poncho walks down a fashion show runway."
      ratio={{16, 9}}
    />
  </:item>
  <:item label="2 of 3">
    <.image
      src="https://github.com/woylie/doggo/blob/main/assets/dog_poncho.jpg?raw=true"
      alt="A dog dressed in a sumptuous, baroque-style costume, complete with jewels and intricate embroidery, parades on an ornate runway at a luxurious fashion show, embodying opulence and grandeur."
      ratio={{16, 9}}
    />
  </:item>
  <:item label="3 of 3">
    <.image
      src="https://github.com/woylie/doggo/blob/main/assets/dog_poncho.jpg?raw=true"
      alt="A dog adorned in a lavish, flamboyant outfit, including a large feathered hat and elaborate jewelry, struts confidently down a luxurious fashion show runway, surrounded by bright lights and an enthusiastic audience."
      ratio={{16, 9}}
    />
  </:item>
</.carousel>
Link to this macro

build_frame(opts \\ [])

View Source (since 0.6.0) (macro)

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

Maturity: Developing

Generate Component

Generate component with default options:

build_frame()

Default options

[
  name: :frame,
  base_class: "frame",
  modifiers: [
    ratio: [
      values: [nil, "1-by-1", "3-by-2", "2-by-3", "4-by-3", "3-by-4", "5-by-4",
       "4-by-5", "16-by-9", "9-by-16"],
      default: nil
    ],
    shape: [values: [nil, "circle"], default: nil]
  ],
  class_name_fun: &Doggo.modifier_class_name/2
]

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>
Link to this macro

build_icon(opts \\ [])

View Source (since 0.6.0) (macro)

Renders a customizable icon using a slot for SVG content.

This component does not bind you to a specific set of icons. Instead, it provides a slot for inserting SVG content from any icon library you choose.

Maturity: Developing

Generate Component

Generate component with default options:

build_icon()

Default options

[
  name: :icon,
  base_class: "icon",
  modifiers: [
    size: [values: ["small", "normal", "medium", "large"], default: "normal"]
  ],
  class_name_fun: &Doggo.modifier_class_name/2,
  visually_hidden_class: "is-visually-hidden"
]

Usage

Render an icon with text as aria-label using the heroicons library:

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

To display the text visibly:

<.icon label="report bug" label_placement={:right}>
  <Heroicons.bug_ant />
</.icon>

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.

Link to this macro

build_icon_sprite(opts \\ [])

View Source (since 0.6.0) (macro)

Renders an icon using an SVG sprite.

Maturity: Developing

Generate Component

Generate component with default options:

build_icon_sprite()

Default options

[
  name: :icon_sprite,
  base_class: "icon",
  modifiers: [
    size: [values: ["small", "normal", "medium", "large"], default: "normal"]
  ],
  class_name_fun: &Doggo.modifier_class_name/2,
  visually_hidden_class: "is-visually-hidden"
]

Usage

Render an icon with text as aria-label:

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

To display the text visibly:

<.icon name="arrow-left" label="Go back" label_placement={:right} />
Link to this macro

build_image(opts \\ [])

View Source (since 0.6.0) (macro)

Renders an image with an optional caption.

Maturity: Developing

Generate Component

Generate component with default options:

build_image()

Default options

[
  name: :image,
  base_class: "image",
  modifiers: [
    ratio: [
      values: [nil, "1-by-1", "3-by-2", "2-by-3", "4-by-3", "3-by-4", "5-by-4",
       "4-by-5", "16-by-9", "9-by-16"],
      default: nil
    ]
  ],
  class_name_fun: &Doggo.modifier_class_name/2
]

Usage

<.image
  src="https://github.com/woylie/doggo/blob/main/assets/dog_poncho.jpg?raw=true"
  alt="A dog wearing a colorful poncho walks down a fashion show runway."
  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

Link to this macro

build_action_bar(opts \\ [])

View Source (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: Experimental

The necessary JavaScript for making this component fully functional and accessible will be added in a future version.

Missing features

  • Roving tabindex
  • Move focus with arrow keys

Generate Component

Generate component with default options:

build_action_bar()

Default options

[
  name: :action_bar,
  base_class: "action-bar",
  modifiers: [],
  class_name_fun: &Doggo.modifier_class_name/2
]

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>
Link to this macro

build_callout(opts \\ [])

View Source (since 0.6.0) (macro)

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

Generate Component

Generate component with default options:

build_callout()

Default options

[
  name: :callout,
  base_class: "callout",
  modifiers: [
    variant: [values: ["info", "success", "warning", "danger"], default: "info"]
  ],
  class_name_fun: &Doggo.modifier_class_name/2
]

Usage

Standard callout:

<.callout title="Dog Care Tip">
  <p>Regular exercise is essential for keeping your dog healthy and happy.</p>
</.callout>

Callout with an icon:

<.callout 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>
Link to this macro

build_combobox(opts \\ [])

View Source (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: Experimental

The necessary JavaScript for making this component fully functional and accessible will be added in a future version.

Missing features

  • Showing/hiding suggestions
  • Filtering suggestions
  • Selecting a value
  • Focus management
  • Keyboard support

Generate Component

Generate component with default options:

build_combobox()

Default options

[
  name: :combobox,
  base_class: "combobox",
  modifiers: [],
  class_name_fun: &Doggo.modifier_class_name/2
]

Usage

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 label/value/description tuples:

<.combobox
  id="dog-breed-selector"
  name="breed"
  list_label="Dog breeds"
  options={[
    {"Labrador Retriever", "labrador", "Friendly and outgoing"},
    {"German Shepherd", "german_shepherd", "Confident and smart"},
    {"Golden Retriever", "golden_retriever", "Intelligent and friendly"},
    {"French Bulldog", "french_bulldog", "Adaptable and playful"},
    {"Bulldog", "bulldog", "Docile and willful"}
  ]}
/>
Link to this macro

build_modal(opts \\ [])

View Source (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

Generate Component

Generate component with default options:

build_modal()

Default options

[
  name: :modal,
  base_class: "modal",
  modifiers: [],
  class_name_fun: &Doggo.modifier_class_name/2
]

Usage

There are two primary ways to manage the display of the modal: via URL state or by setting and removing the open attribute.

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={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>

Without URL

To toggle the modal visibility dynamically with the open attribute:

  1. Omit the open attribute in the template.
  2. Use the show_modal/1 and 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={JS.exec("data-cancel", to: "#pet-modal")}>
      Close
    </.link>
  </:footer>
</.modal>

To open modal, use the show_modal/1 function.

<.button
  phx-click={Doggo.show_modal("pet-modal")}
  aria-haspopup="dialog"
>
  show
</.button>

CSS

To hide the modal when the open attribute is not set, use the following CSS styles:

dialog.modal:not([open]),
dialog.modal[open="false"] {
  display: none;
}

Semantics

While the showModal() JavaScript function is typically recommended for managing modal dialog semantics, this component utilizes the open attribute to control visibility. This approach is chosen to eliminate the need for library consumers to add additional JavaScript code. To ensure proper modal semantics, the aria-modal attribute is added to the dialog element.

Link to this macro

build_radio_group(opts \\ [])

View Source (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: Experimental

Generate Component

Generate component with default options:

build_radio_group()

Default options

[
  name: :radio_group,
  base_class: "radio-group",
  modifiers: [],
  class_name_fun: &Doggo.modifier_class_name/2
]

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"] {}
Link to this macro

build_toolbar(opts \\ [])

View Source (since 0.6.0) (macro)

Renders a container for a set of controls.

Maturity: Experimental

The necessary JavaScript for making this component fully functional and accessible will be added in a future version.

Missing features

  • Roving tabindex
  • Move focus with arrow keys

Generate Component

Generate component with default options:

build_toolbar()

Default options

[
  name: :toolbar,
  base_class: "toolbar",
  modifiers: [],
  class_name_fun: &Doggo.modifier_class_name/2
]

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 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>
Link to this macro

build_tooltip(opts \\ [])

View Source (since 0.6.0) (macro)

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

Generate Component

Generate component with default options:

build_tooltip()

Default options

[
  name: :tooltip,
  base_class: "tooltip-container",
  modifiers: [],
  class_name_fun: &Doggo.modifier_class_name/2
]

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, add the :contains_link attribute:

<p>
  Did you know that the
  <.tooltip id="labrador-info" contains_link>
    <.link navigate={~p"/labradors"}>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>