Angle Slider

v0.0.10

v0.0.10StableHeadlessStatelessAccessibleJSApache-2.0
Installation mix mishka.ui.gen.headless angle_slider
Source View Source

Overview of the Phoenix and LiveView Headless Angle Slider Component

The Mishka Chelekom Headless Angle Slider is a Phoenix function component for choosing an angle on a circle, from 0 to 360 degrees. Zero points up and the value grows clockwise. It renders one div with role="slider" and the matching aria-valuemin, aria-valuemax, aria-valuenow and aria-valuetext, and it ships no styling. The AngleSlider JS hook handles pointer dragging and the arrow keys, rotates the thumb to the current angle, and keeps the hidden input and the degree label up to date.

Four parts take Tailwind classes: the root (class), thumb_layer_class, thumb_class and value_class. The hook rotates the thumb layer, so make the layer fill the root and put the thumb on its top edge. A LiveView gets the value through a form field, an event, or both.

Base UI Angle Slider example - circular dial (Phoenix headless)

Base UI Angle Slider example - circular dial (Phoenix headless)

135°
heex
<.angle_slider
  id="baseui-angle-slider"
  value={135}
  step={5}
  label="Angle"
  class="relative block size-28 rounded-full border-2 border-neutral-300 bg-white outline-none cursor-grab focus:ring-2 focus:ring-neutral-400 data-[dragging]:cursor-grabbing dark:border-neutral-700 dark:bg-neutral-900"
  thumb_layer_class="absolute inset-0"
  thumb_class="absolute left-1/2 top-0 size-4 -translate-x-1/2 -translate-y-1/2 rounded-full bg-neutral-900 dark:bg-neutral-100"
  value_class="absolute inset-0 grid place-items-center text-sm font-medium text-neutral-700 dark:text-neutral-200"
/>

Attributes

id is required because it carries the hook. value is the starting angle in degrees, wrapped into the 0 to 360 range. step is how far one key press moves the angle and the grid that dragging snaps to. It defaults to 1. label is the accessible name and defaults to "Angle". show_value draws the degree text in the value part and defaults to true. disabled ignores pointer and keyboard input and takes the dial out of the tab order.

Events and forms

Give the slider a name and it renders a hidden input with that name. The hook writes the angle into the input and fires a bubbling input event when the pointer is released and on every key press, so a surrounding <.form phx-change> sees each change.

on_change names an event that is pushed to the server at the same two moments. The payload carries the angle as a whole number of degrees under value. The event goes to the LiveView, or to the LiveComponent that renders the slider.

heex
<.form for={@form} id="gradient-form" phx-submit="save">
  <.angle_slider
    id="gradient-angle"
    name="gradient[angle]"
    value={@angle}
    on_change="angle"
    class="relative block size-28 rounded-full border-2 border-neutral-300"
    thumb_layer_class="absolute inset-0"
    thumb_class="absolute left-1/2 top-0 size-4 -translate-x-1/2 -translate-y-1/2 rounded-full bg-neutral-900"
  />
</.form>
elixir
def handle_event("angle", %{"value" => degrees}, socket) do
  {:noreply, assign(socket, :angle, degrees)}
end

Parts & data attributes

The root carries data-value, data-step, data-disabled and, during a drag, data-dragging. The parts are marked with data-part: thumb-layer is the element the hook rotates, thumb is the handle inside it, value is the degree label and input is the hidden field that exists when name is set. Each part also has a structural class, such as chelekom-angle-slider__thumb, that you can target from a stylesheet.

Accessibility

The component follows the WAI-ARIA slider pattern. The root takes focus with tabindex="0". Arrow Right and Arrow Up add one step, Arrow Left and Arrow Down subtract one, Home sets 0 and End sets 360 minus one step. The value wraps, so stepping down from 0 lands on the last step. aria-valuetext reads as "135 degrees", so a screen reader announces the unit.