Segmented

Component

Interactive examples and API documentation

Basic
The most basic usage.
Code
<%= render Hakumi::Segmented::Component.new(
  options: ["Daily", "Weekly", "Monthly"]
) %>
Vertical Direction
Make it vertical.
Code
<%= render(Hakumi::Space::Component.new(direction: :horizontal, size: :large)) do |space| %>
  <% space.with_item do %>
    <%= render(Hakumi::Segmented::Component.new(
      options: ["Daily", "Weekly", "Monthly"],
      vertical: true
    )) %>
  <% end %>

  <% space.with_item do %>
    <%= render(Hakumi::Segmented::Component.new(
      options: [
        { label: nil, value: "list", icon: :unordered_list, aria_label: "List view" },
        { label: nil, value: "board", icon: :appstore, aria_label: "Board view" },
        { label: nil, value: "timeline", icon: :history, aria_label: "Timeline view" }
      ],
      vertical: true
    )) %>
  <% end %>
<% end %>
Block Segmented
Block property will make the Segmented fit to its parent width.
Code
<%= render Hakumi::Card::Component.new(style: "max-width: 420px;") do %>
  <%= render Hakumi::Segmented::Component.new(
    options: ["Daily", "Weekly", "Monthly"],
    block: true
  ) %>
<% end %>
Round shape
Round shape of Segmented.
Code
<%= render(Hakumi::Segmented::Component.new(
  options: [
    { label: nil, value: "grid", icon: :appstore, aria_label: "Grid view" },
    { label: nil, value: "list", icon: :unordered_list, aria_label: "List view" },
    { label: nil, value: "timeline", icon: :history, aria_label: "Timeline view" }
  ],
  round: true
)) %>
Disabled
Disabled Segmented.
Code
<%= render Hakumi::Segmented::Component.new(
  options: ["Daily", "Weekly", "Monthly"],
  value: "Weekly",
  disabled: true
) %>
Controlled mode
Controlled Segmented.
Selected: Daily
Code
<%= render(Hakumi::Space::Component.new(direction: :vertical, size: :middle)) do |space| %>
  <% space.with_item do %>
    <%= render(Hakumi::Segmented::Component.new(
      options: ["Daily", "Weekly", "Monthly"],
      value: "Daily",
      id: "segmented-controlled"
    )) %>
  <% end %>

  <% space.with_item do %>
    <%= render(Hakumi::Space::Component.new(size: :small)) do |actions| %>
      <% actions.with_item do %>
        <%= render(Hakumi::Button::Component.new(id: "segmented-set-weekly")) { "Set Weekly" } %>
      <% end %>

      <% actions.with_item do %>
        <%= render(Hakumi::Button::Component.new(id: "segmented-set-monthly")) { "Set Monthly" } %>
      <% end %>
    <% end %>
  <% end %>

  <% space.with_item do %>
    <%= render(Hakumi::Typography::Text::Component.new(id: "segmented-controlled-value")) { "Selected: Daily" } %>
  <% end %>
<% end %>

<script>
  (() => {
    const segmented = document.getElementById("segmented-controlled")
    const weeklyButton = document.getElementById("segmented-set-weekly")
    const monthlyButton = document.getElementById("segmented-set-monthly")
    const valueLabel = document.getElementById("segmented-controlled-value")

    if (!segmented || !weeklyButton || !monthlyButton || !valueLabel) return

    const updateLabel = (value) => {
      valueLabel.textContent = `Selected: ${value || "-"}`
    }

    let wired = false
    const wire = () => {
      if (wired) return true
      const api = segmented.hakumiSegmented
      if (!api) return false

      segmented.addEventListener("hakumi--segmented:change", (event) => {
        updateLabel(event.detail.value)
      })

      weeklyButton.addEventListener("click", () => {
        api.setValue("Weekly")
      })

      monthlyButton.addEventListener("click", () => {
        api.setValue("Monthly")
      })

      updateLabel(api.getValue())
      wired = true
      return true
    }

    if (wire()) return

    const onReady = () => {
      if (wire()) {
        document.removeEventListener("turbo:load", onReady)
        window.removeEventListener("load", onReady)
      }
    }

    document.addEventListener("turbo:load", onReady)
    window.addEventListener("load", onReady)
  })()
</script>
Custom Render
Custom each Segmented Item.
Code
<% launch_label = capture do %>
  <%= render(Hakumi::Flex::Component.new(align: :center, gap: 8)) do %>
    <%= render(Hakumi::Icon::Component.new(name: :rocket)) %>
    <%= render(Hakumi::Typography::Text::Component.new(strong: true)) { "Launch" } %>
  <% end %>
<% end %>

<% review_label = capture do %>
  <%= render(Hakumi::Flex::Component.new(align: :center, gap: 8)) do %>
    <%= render(Hakumi::Icon::Component.new(name: :audit)) %>
    <%= render(Hakumi::Typography::Text::Component.new(type: :secondary)) { "Review" } %>
  <% end %>
<% end %>

<%= render(Hakumi::Segmented::Component.new(
  options: [
    { label: launch_label, value: "launch" },
    { label: review_label, value: "review" }
  ]
)) %>
Dynamic
Load options dynamically.
Click load to fetch options.
Code
<%= render Hakumi::Space::Component.new(direction: :vertical, size: :middle) do |space| %>
  <% space.with_item do %>
    <%= render(Hakumi::Button::Component.new(type: :primary, id: "segmented-load-options")) { "Load Options" } %>
  <% end %>

  <% space.with_item do %>
    <%= render Hakumi::Container::Component.new(
      id: "segmented-dynamic-target",
      width: :fluid,
      padded: false,
      centered: false
    ) do %>
      <%= render(Hakumi::Typography::Text::Component.new(type: :secondary)) { "Click load to fetch options." } %>
    <% end %>
  <% end %>
<% end %>

<script>
  (() => {
    const button = document.getElementById("segmented-load-options")
    if (!button) return

    const options = [
      { label: "Daily", value: "daily" },
      { label: "Weekly", value: "weekly" },
      { label: "Monthly", value: "monthly" }
    ]

    let wired = false
    const wire = () => {
      if (wired) return true
      if (!window.HakumiComponents?.renderComponent) return false

      button.addEventListener("click", async () => {
        await window.HakumiComponents.renderComponent("segmented", {
          params: {
            options: JSON.stringify(options),
            value: "weekly",
            block: true,
            round: true
          },
          target: "#segmented-dynamic-target",
          mode: "replace"
        })
      })

      wired = true
      return true
    }

    if (wire()) return

    const onReady = () => {
      if (wire()) {
        document.removeEventListener("turbo:load", onReady)
        window.removeEventListener("load", onReady)
      }
    }

    document.addEventListener("turbo:load", onReady)
    window.addEventListener("load", onReady)
  })()
</script>
Three sizes of Segmented
Large (40px), default (32px) and small (24px).
Code
<%= render Hakumi::Space::Component.new(size: :middle) do |space| %>
  <% space.with_item do %>
    <%= render Hakumi::Segmented::Component.new(
      options: ["Small", "Option"],
      size: :small
    ) %>
  <% end %>

  <% space.with_item do %>
    <%= render Hakumi::Segmented::Component.new(
      options: ["Default", "Option"]
    ) %>
  <% end %>

  <% space.with_item do %>
    <%= render Hakumi::Segmented::Component.new(
      options: ["Large", "Option"],
      size: :large
    ) %>
  <% end %>
<% end %>
With Icon
Set icon for Segmented Item.
Code
<%= render Hakumi::Segmented::Component.new(
  options: [
    { label: "List", value: "list", icon: :unordered_list },
    { label: "Board", value: "board", icon: :appstore }
  ]
) %>
With name
Passing the name property to all input[type=radio] within the segmented control.
Code
<%= render Hakumi::Segmented::Component.new(
  options: ["Daily", "Weekly", "Monthly"],
  name: "interval"
) %>
With Icon only
Set icon without label for Segmented Item.
Code
<%= render Hakumi::Segmented::Component.new(
  options: [
    { label: nil, value: "list", icon: :unordered_list, aria_label: "List view" },
    { label: nil, value: "board", icon: :appstore, aria_label: "Board view" },
    { label: nil, value: "settings", icon: :setting, aria_label: "Settings view" }
  ],
  round: true
) %>

Segmented API

Prop Type Default Description
options Array[String] or Array[Hash] [] Option list. Strings become both label and value. Hashes support optional keys: label:, value:, disabled:, icon:, class:.
value String - Controlled selected value.
default_value String - Initial selected value.
disabled Boolean false Disable all options.
block Boolean false Stretch to fill parent width.
vertical Boolean false Render options vertically.
round Boolean false Use pill-shaped segments.
name String auto Shared name for input[type=radio] options.
size Symbol :middle Size of items (:small, :middle, :large).
aria_label String - Accessible label for the group.
content Slot - Additional custom items rendered after options.

Segmented JavaScript API (element.hakumiSegmented)

Prop Type Default Description
getValue() Function - Returns the current selected value.
setValue(value) Function - Programmatically set the selected value.
reset() Function - Reset to the initial selected value.
clear() Function - Clear the selected value.
getOptions() Function - Returns the currently rendered options.
setOptions(options) Function - Replace the option list (text-only options).
onChange(callback) Function - Subscribe to value changes.
offChange(callback) Function - Unsubscribe from value changes.