Corex.RadioGroup (Corex v0.1.0)

View Source

Phoenix implementation of Zag.js Radio Group.

Anatomy

Minimal

<.radio_group
  name="rg-minimal"
  class="radio-group"
  items={[
    %{value: "lorem", label: "Lorem ipsum dolor sit amet"},
    %{value: "duis", label: "Duis dictum gravida odio ac pharetra?"},
    %{value: "donec", label: "Donec condimentum ex mi"}
  ]}
>
  <:label>Choose one</:label>
</.radio_group>

With indicator

<.radio_group
  name="rg-indicator"
  class="radio-group"
  items={[
    %{value: "lorem", label: "Lorem ipsum dolor sit amet"},
    %{value: "duis", label: "Duis dictum gravida odio ac pharetra?"},
    %{value: "donec", label: "Donec condimentum ex mi"}
  ]}
>
  <:label>Choose one</:label>
  <:item_control><.heroicon name="hero-check" class="data-checked" /></:item_control>
</.radio_group>

Invalid

<.radio_group
  name="rg-invalid"
  class="radio-group"
  invalid
  errors={["Required"]}
  items={[
    %{value: "lorem", label: "Lorem ipsum dolor sit amet"},
    %{value: "duis", label: "Duis dictum gravida odio ac pharetra?"},
    %{value: "donec", label: "Donec condimentum ex mi"}
  ]}
>
  <:label>Choose one</:label>
  <:item_control><.heroicon name="hero-check" class="data-checked" /></:item_control>
  <:error :let={msg}>
    <.heroicon name="hero-exclamation-circle" class="icon" />
    {msg}
  </:error>
</.radio_group>

Read-only

<.radio_group
  name="rg-readonly"
  class="radio-group"
  read_only
  value="lorem"
  items={[
    %{value: "lorem", label: "Lorem ipsum dolor sit amet"},
    %{value: "duis", label: "Duis dictum gravida odio ac pharetra?"},
    %{value: "donec", label: "Donec condimentum ex mi"}
  ]}
>
  <:label>Choose one</:label>
  <:item_control><.heroicon name="hero-check" class="data-checked" /></:item_control>
</.radio_group>

Items can be {value, label} tuples or maps with :value, :label, and optional :disabled, :invalid.

Events

Pick an event name and pass it to on_* on <.radio_group>.

Server events

EventWhenPayload
on_value_change="radio_group_changed"Selected value changes%{"id" => id, "value" => value}

on_value_change

<.radio_group
  name="rg-events"
  class="radio-group"
  on_value_change="radio_group_changed"
  items={[
    %{value: "lorem", label: "Lorem ipsum dolor sit amet"},
    %{value: "duis", label: "Duis dictum gravida odio ac pharetra?"},
    %{value: "donec", label: "Donec condimentum ex mi"}
  ]}
>
  <:label>Choose one</:label>
  <:item_control><.heroicon name="hero-check" class="data-checked" /></:item_control>
</.radio_group>
def handle_event("radio_group_changed", %{"id" => _id, "value" => value}, socket) do
  {:noreply, assign(socket, :choice, value)}
end

Client events

EventWhenevent.detail
on_value_change_client="radio-group-changed"Selected value changesid, value

on_value_change_client

<.radio_group
  id="radio-group-events-client"
  name="rg-events-client"
  class="radio-group"
  on_value_change_client="radio-group-changed"
  items={[
    %{value: "lorem", label: "Lorem ipsum dolor sit amet"},
    %{value: "duis", label: "Duis dictum gravida odio ac pharetra?"},
    %{value: "donec", label: "Donec condimentum ex mi"}
  ]}
>
  <:label>Choose one</:label>
  <:item_control><.heroicon name="hero-check" class="data-checked" /></:item_control>
</.radio_group>
const el = document.getElementById("radio-group-events-client");
el?.addEventListener("radio-group-changed", (event) => console.log(event.detail));

API

Requires a stable id on <.radio_group>. Imperative helpers set the selected value.

FunctionActionReturns
set_value/2Set value (client)%Phoenix.LiveView.JS{}
set_value/3Set value (server)socket
clear_value/1Clear selection (client)%Phoenix.LiveView.JS{}
clear_value/2Clear selection (server)socket
focus/1Focus the group (client)%Phoenix.LiveView.JS{}
focus/2Focus the group (server)socket
value/1Read current value (client)%Phoenix.LiveView.JS{}
value/2Read current value (client, options)%Phoenix.LiveView.JS{}
value/3Read current value (server)socket

set_value

<.action phx-click={Corex.RadioGroup.set_value("radio-group-api-server", "duis")} class="button button--sm">
  Set Duis
</.action>
<.radio_group
  id="radio-group-api-server"
  name="rg-api-server"
  class="radio-group"
  value="lorem"
  items={[
    %{value: "lorem", label: "Lorem ipsum dolor sit amet"},
    %{value: "duis", label: "Duis dictum gravida odio ac pharetra?"},
    %{value: "donec", label: "Donec condimentum ex mi"}
  ]}
>
  <:label>Pick</:label>
  <:item_control><.heroicon name="hero-check" class="data-checked" /></:item_control>
</.radio_group>

Patterns

Controlled

For server-owned selection, set controlled, bind value, and handle on_value_change in LiveView.

<.radio_group
  id="radio-group-api-controlled"
  name="rg-controlled"
  class="radio-group"
  controlled
  value={@api_controlled_value}
  on_value_change="radio_group_api_controlled"
  items={[
    %{value: "lorem", label: "Lorem ipsum dolor sit amet"},
    %{value: "duis", label: "Duis dictum gravida odio ac pharetra?"},
    %{value: "donec", label: "Donec condimentum ex mi"}
  ]}
>
  <:label>Pick</:label>
  <:item_control><.heroicon name="hero-check" class="data-checked" /></:item_control>
</.radio_group>
def handle_event("radio_group_api_controlled", %{"value" => v}, socket) do
  {:noreply, assign(socket, :api_controlled_value, v)}
end

Stream

Use Phoenix.LiveView.stream/3 to add or remove items at runtime. Keep @items_list in sync with the stream and pass it as items. Configure dom_id to match each item (radio-group:stream-radio-group:item:#{value}).

<.radio_group
  name="stream-rg"
  class="radio-group"
  items={@items_list}
  value={@stream_value}
  controlled
  on_value_change="patterns_stream_value"
>
  <:label>Choose one</:label>
  <:item_control><.heroicon name="hero-check" class="data-checked" /></:item_control>
</.radio_group>

Form

Use field={@form[:choice]} inside <.form> so the hidden input name and validation align with Phoenix forms. Pass invalid only when you want invalid styling.

For cross-cutting invalid styling and error presentation, see the Forms guide. Pass invalid={Corex.FormField.invalid?(@form[:choice])} when you want alert borders after validation.

<.form for={@form} phx-change="validate">
  <.radio_group
    field={@form[:choice]}
    class="radio-group"
    items={[
      %{value: "lorem", label: "Lorem ipsum dolor sit amet"},
      %{value: "duis", label: "Duis dictum gravida odio ac pharetra?"},
      %{value: "donec", label: "Donec condimentum ex mi"}
    ]}
  >
    <:label>Choose one</:label>
    <:item_control><.heroicon name="hero-check" class="data-checked" /></:item_control>
    <:error :let={msg}>
      <.heroicon name="hero-exclamation-circle" class="icon" />
      {msg}
    </:error>
  </.radio_group>
</.form>

Style

Target parts with data-scope and data-part, or use Corex Design: import tokens and radio-group.css, then set class="radio-group" on <.radio_group>.

[data-scope="radio-group"][data-part="root"] {}
[data-scope="radio-group"][data-part="label"] {}
[data-scope="radio-group"][data-part="item"] {}
[data-scope="radio-group"][data-part="item-text"] {}
[data-scope="radio-group"][data-part="item-control"] {}
[data-scope="radio-group"][data-part="item-hidden-input"] {}
@import "../corex/main.css";
@import "../corex/tokens/themes/neo/light.css";
@import "../corex/components/radio-group.css";

Stack modifiers on the host (class on <.radio_group>).

Color

ModifierClasses
Defaultradio-group
Accentradio-group radio-group--accent
Brandradio-group radio-group--brand
Alertradio-group radio-group--alert
Inforadio-group radio-group--info
Successradio-group radio-group--success

Size

ModifierClasses
SMradio-group radio-group--sm
MDradio-group radio-group--md
LGradio-group radio-group--lg
XLradio-group radio-group--xl

Summary

API

Clear selection from phx-click. Dispatches corex:radio-group:clear-value.

Clear selection from handle_event (radio_group_clear_value).

Move focus onto the radio group from phx-click. Dispatches corex:radio-group:focus.

Focus the radio group from handle_event (radio_group_focus).

Choose a radio option from phx-click. Dispatches corex:radio-group:set-value with detail.value.

Choose a radio option from handle_event (radio_group_set_value).

Same as value/2 with default respond_to:.

Read the current value from phx-click. Dispatches corex:radio-group:value. Optional respond_to: :server, :client, or :both.

Read the current value from handle_event (radio_group_value). Same replies as value/2.

Components

radio_group(assigns)

Attributes

  • id (:string)
  • value (:string) - Defaults to nil.
  • controlled (:boolean) - Defaults to false.
  • name (:string) - Defaults to nil.
  • form (:string) - Defaults to nil.
  • disabled (:boolean) - Defaults to false.
  • invalid (:boolean) - Defaults to false.
  • required (:boolean) - Defaults to false.
  • read_only (:boolean) - Defaults to false.
  • dir (:string) - Defaults to nil.Must be one of nil, "ltr", or "rtl".
  • orientation (:string) - Defaults to "vertical". Must be one of "horizontal", or "vertical".
  • on_value_change (:string) - Defaults to nil.
  • on_value_change_client (:string) - Defaults to nil.
  • items (:list) (required) - List of [value, label] or %{value: ..., label: ..., disabled: ..., invalid: ...}.
  • errors (:list) - Error messages to display (non-field API). Defaults to [].
  • field (Phoenix.HTML.FormField) - A form field, e.g. f[:choice] or @form[:choice].
  • Global attributes are accepted.

Slots

  • label - Accepts attributes:
    • class (:string)
  • item_control - Accepts attributes:
    • class (:string)
  • item - Accepts attributes:
    • class (:string)
  • error - Accepts attributes:
    • class (:string)

API

clear_value(radio_group_id)

Clear selection from phx-click. Dispatches corex:radio-group:clear-value.

<.action phx-click={Corex.RadioGroup.clear_value("my-rg")}>Clear</.action>
<.radio_group id="my-rg" name="rg-api" class="radio-group" items={[%{value: "lorem", label: "Lorem"}]}>
  <:label>Pick</:label>
</.radio_group>

clear_value(socket, radio_group_id)

Clear selection from handle_event (radio_group_clear_value).

def handle_event("clear_rg", _, socket) do
  {:noreply, Corex.RadioGroup.clear_value(socket, "my-rg")}
end

focus(radio_group_id)

Move focus onto the radio group from phx-click. Dispatches corex:radio-group:focus.

<.action phx-click={Corex.RadioGroup.focus("my-rg")}>Focus</.action>
<.radio_group id="my-rg" name="rg-api" class="radio-group" items={[%{value: "a", label: "A"}]}>
  <:label>Pick</:label>
</.radio_group>

focus(socket, radio_group_id)

Focus the radio group from handle_event (radio_group_focus).

def handle_event("focus_rg", _, socket) do
  {:noreply, Corex.RadioGroup.focus(socket, "my-rg")}
end

set_value(radio_group_id, value)

Choose a radio option from phx-click. Dispatches corex:radio-group:set-value with detail.value.

<.action phx-click={Corex.RadioGroup.set_value("my-rg", "lorem")}>Pick Lorem</.action>
<.radio_group id="my-rg" name="rg-api" class="radio-group" items={[%{value: "lorem", label: "Lorem"}, %{value: "duis", label: "Duis"}]}>
  <:label>One</:label>
</.radio_group>
document.getElementById("my-rg")?.dispatchEvent(
  new CustomEvent("corex:radio-group:set-value", {
    bubbles: false,
    detail: { value: "lorem" },
  })
);

set_value(socket, radio_group_id, value)

Choose a radio option from handle_event (radio_group_set_value).

def handle_event("pick", %{"v" => v}, socket) do
  {:noreply, Corex.RadioGroup.set_value(socket, "my-rg", v)}
end

value(radio_group_id)

Same as value/2 with default respond_to:.

value(radio_group_id, opts)

Read the current value from phx-click. Dispatches corex:radio-group:value. Optional respond_to: :server, :client, or :both.

ReplyPayload
Serverradio_group_value_response%{"id" => id, "value" => selection}
Clientradio-group-value on the rootsame fields in detail
<.action phx-click={Corex.RadioGroup.value("my-rg")}>Read</.action>
<.radio_group id="my-rg" name="rg-api" class="radio-group" items={[%{value: "a", label: "A"}]}>
  <:label>Pick</:label>
</.radio_group>
def handle_event("radio_group_value_response", %{"id" => _, "value" => v}, socket) do
  {:noreply, assign(socket, :rg, v)}
end

value(socket, radio_group_id, opts)

Read the current value from handle_event (radio_group_value). Same replies as value/2.

ReplyPayload
radio_group_value_response%{"id" => id, "value" => selection}
def handle_event("read_rg", _, socket) do
  {:noreply, Corex.RadioGroup.value(socket, "my-rg", respond_to: :server)}
end