shshadcn-htmx

Components

Separator

A horizontal or vertical line that visually divides content. Two flavours: decorative (purely visual, ignored by assistive tech) and semantic (renders <hr> or role="separator" so AT announces a thematic break).

Installation

1. Install via the shadcn CLI

npx shadcn@latest add http://localhost/r/separator.json

2. Use it

components/ui/separator.tsx
import { Separator } from "@/components/ui/separator"

// Decorative horizontal (default)
<Separator />

// Vertical between flex items
<div class="flex h-5 items-center gap-3">
  <span>Profile</span>
  <Separator orientation="vertical" />
  <span>Settings</span>
</div>

// Semantic <hr>
<Separator decorative={false} />
Or copy the source manually
components/ui/separator.tsx
/** @jsxImportSource hono/jsx */
import { cn, type ClassValue } from "@/registry/lib/cn"

// Separator — shadcn-htmx, htmx v4 + Tailwind v4.
//
// Source of truth (visual styles):
//   repos/shadcn-ui/apps/v4/registry/new-york-v4/ui/separator.tsx
//
// Semantics (spec-first):
//   - decorative=true  (default): the line is purely visual. We render a
//                       <div> with no role so assistive tech skips it.
//   - decorative=false (semantic): the line marks a thematic break between
//                       content groups. We render <hr> (implicit
//                       role="separator") for horizontal, and a div with
//                       role="separator" + aria-orientation for vertical
//                       (there's no native vertical hr).
//
// MDN:
//   repos/mdn/files/en-us/web/html/reference/elements/hr/index.md
//   repos/mdn/files/en-us/web/accessibility/aria/reference/roles/separator_role/
// APG:
//   repos/aria-practices/content/patterns/none/ (separator role)

export type SeparatorOrientation = "horizontal" | "vertical"

const base =
  "shrink-0 bg-border " +
  "data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full " +
  "data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px"

export function separatorClasses(opts?: {
  orientation?: SeparatorOrientation
  class?: ClassValue
}): string {
  return cn(base, opts?.class)
}

type SeparatorProps = {
  orientation?: SeparatorOrientation
  decorative?: boolean
  class?: ClassValue
  id?: string
}

export function Separator(props: SeparatorProps) {
  const { orientation = "horizontal", decorative = true, class: className, id } = props
  const classes = separatorClasses({ orientation, class: className })
  // Horizontal + semantic → native <hr> (has implicit role="separator" so
  // AT announces; we strip the default margin via Tailwind classes).
  if (!decorative && orientation === "horizontal") {
    return (
      <hr
        id={id}
        data-slot="separator"
        data-orientation="horizontal"
        // <hr> already carries role=separator. We set aria-orientation
        // explicitly to be defensive against older AT that read the
        // implicit role but want explicit attribute.
        aria-orientation="horizontal"
        class={cn(classes, "border-0")}
      />
    )
  }
  return (
    <div
      id={id}
      data-slot="separator"
      data-orientation={orientation}
      // decorative: drop the implicit role. Browser default for <div> is
      // generic. Setting role="none" is redundant but signals intent.
      role={decorative ? undefined : "separator"}
      aria-orientation={!decorative ? orientation : undefined}
      class={classes}
    />
  )
}

1. Save the file

Copy separator.html into templates/components/.

2. Use it

templates/components/separator.html
{% from "components/separator.html" import separator %}

{{ separator() }}                                  {# decorative horizontal #}
{{ separator(orientation="vertical") }}            {# decorative vertical #}
{{ separator(decorative=false) }}                  {# semantic <hr> #}
View source
templates/components/separator.html
{# Separator macro — shadcn-htmx, htmx v4 + Tailwind v4.
   Mirrors registry/ui/separator.tsx.

   Usage:
     {% from "components/separator.html" import separator %}
     {{ separator() }}                          {# decorative horizontal #}
     {{ separator(orientation="vertical") }}    {# decorative vertical #}
     {{ separator(decorative=false) }}          {# semantic <hr> #} #}

{% macro separator(
    orientation="horizontal",
    decorative=true,
    id=none,
    extra_class=""
) %}
{%- set base -%}
shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px
{%- endset -%}
{%- if not decorative and orientation == "horizontal" -%}
<hr
  {%- if id %} id="{{ id }}"{% endif %}
  data-slot="separator"
  data-orientation="horizontal"
  aria-orientation="horizontal"
  class="{{ base }} border-0 {{ extra_class }}">
{%- else -%}
<div
  {%- if id %} id="{{ id }}"{% endif %}
  data-slot="separator"
  data-orientation="{{ orientation }}"
  {%- if not decorative %} role="separator" aria-orientation="{{ orientation }}"{% endif %}
  class="{{ base }} {{ extra_class }}"></div>
{%- endif -%}
{% endmacro %}

1. Save the file

Add separator.tmpl alongside button.tmpl.

2. Use it

templates/components/separator.tmpl
{{template "separator" (dict)}}                                  // decorative horizontal
{{template "separator" (dict "Orientation" "vertical")}}        // decorative vertical
{{template "separator" (dict "Decorative" (ptr false))}}        // semantic <hr>
View source
templates/components/separator.tmpl
{{/*
  Separator template — shadcn-htmx, htmx v4 + Tailwind v4.
  Mirrors registry/ui/separator.tsx.

      type SeparatorArgs struct {
          Orientation string // "horizontal" (default) | "vertical"
          Decorative  *bool  // nil/true = visual only; false = semantic <hr> / role=separator
          ID          string
      }
*/}}

{{define "separator"}}
{{- $orientation := or .Orientation "horizontal" -}}
{{- $decorative := true -}}{{- if .Decorative}}{{- $decorative = deref .Decorative}}{{end -}}
{{- $base := "shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px" -}}
{{- if and (not $decorative) (eq $orientation "horizontal") -}}
<hr {{if .ID}}id="{{.ID}}" {{end}}data-slot="separator" data-orientation="horizontal" aria-orientation="horizontal" class="{{$base}} border-0">
{{- else -}}
<div {{if .ID}}id="{{.ID}}" {{end}}data-slot="separator" data-orientation="{{$orientation}}"{{if not $decorative}} role="separator" aria-orientation="{{$orientation}}"{{end}} class="{{$base}}"></div>
{{- end -}}
{{end}}

1. Save the file

Drop separator.ex into lib/my_app_web/components/.

2. Use it

lib/my_app_web/components/separator.ex
<.separator />
<.separator orientation="vertical" />
<.separator decorative={false} />
View source
lib/my_app_web/components/separator.ex
defmodule ShadcnHtmx.Components.Separator do
  @moduledoc """
  Separator — shadcn-htmx, htmx v4 + Tailwind v4 for Phoenix.

  Mirrors registry/ui/separator.tsx.

  Semantics:
    - `decorative: true` (default) → purely visual; renders as a styled <div>
      with no role so AT skips it.
    - `decorative: false` (semantic) → marks a thematic break. Horizontal
      uses <hr> (implicit role="separator"); vertical uses
      <div role="separator" aria-orientation="vertical"> (no native equivalent).

  ## Examples

      <.separator />                          # decorative horizontal
      <.separator orientation="vertical" />   # decorative vertical
      <.separator decorative={false} />       # semantic <hr>
  """

  use Phoenix.Component

  @base "shrink-0 bg-border " <>
          "data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full " <>
          "data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px"

  attr :orientation, :string, default: "horizontal", values: ~w(horizontal vertical)
  attr :decorative, :boolean, default: true
  attr :class, :string, default: nil
  attr :rest, :global

  def separator(assigns) do
    assigns = assign(assigns, :base, @base)

    cond do
      not assigns.decorative and assigns.orientation == "horizontal" ->
        ~H"""
        <hr
          data-slot="separator"
          data-orientation="horizontal"
          aria-orientation="horizontal"
          class={[@base, "border-0", @class]}
          {@rest}
        />
        """

      true ->
        ~H"""
        <div
          data-slot="separator"
          data-orientation={@orientation}
          role={if !@decorative, do: "separator"}
          aria-orientation={if !@decorative, do: @orientation}
          class={[@base, @class]}
          {@rest}
        />
        """
    end
  end
end

1. Save the file

Tailwind utilities only; no script.

2. Use it

index.html
<!-- Decorative horizontal -->
<div data-slot="separator" data-orientation="horizontal"
  class="shrink-0 bg-border h-px w-full"></div>

<!-- Semantic horizontal -->
<hr class="shrink-0 border-0 bg-border h-px w-full">
View source
index.html
<!--
  shadcn-htmx — raw HTML separator snippets.

  Two semantic modes:
    1. Decorative (default) — purely visual line. Use <div> with no role
       so assistive tech skips it.
    2. Semantic — marks a thematic break. <hr> for horizontal, role-only
       div for vertical.

  BASE:
    shrink-0 bg-border
    data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full
    data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px
-->

<!-- Decorative horizontal -->
<div data-slot="separator" data-orientation="horizontal"
  class="shrink-0 bg-border h-px w-full"></div>

<!-- Decorative vertical (works inside a flex/grid container with height) -->
<div data-slot="separator" data-orientation="vertical"
  class="shrink-0 bg-border h-full w-px"></div>

<!-- Semantic horizontal — <hr> has implicit role="separator" -->
<hr data-slot="separator" data-orientation="horizontal" aria-orientation="horizontal"
  class="shrink-0 border-0 bg-border h-px w-full">

<!-- Semantic vertical — no native vertical hr; explicit role + orientation -->
<div data-slot="separator" data-orientation="vertical"
     role="separator" aria-orientation="vertical"
  class="shrink-0 bg-border h-full w-px"></div>

Examples

Decorative — visual divider that AT skips

Default mode. Renders as a styled <div> with no role so screen readers don't announce it.

Use decorative for pure layout — the line between two paragraphs in a card, the row separators in a sidebar. The visual carries the meaning; the DOM stays semantically silent so AT users aren't interrupted with "separator, separator, separator" as they scan.

Above the separator.

Below the separator.

<p>Above the separator.</p>
<Separator />
<p>Below the separator.</p>
<p>Above the separator.</p>
{{ separator() }}
<p>Below the separator.</p>
<p>Above the separator.</p>
{{template "separator" (dict)}}
<p>Below the separator.</p>
<p>Above the separator.</p>
<.separator />
<p>Below the separator.</p>
<div class="w-full max-w-md space-y-3 text-sm">
  <p>Above the separator.</p>
  <div data-slot="separator" data-orientation="horizontal" class="shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px">
  </div>
  <p>Below the separator.</p>
</div>

Semantic — thematic break

Use when the line marks a genuine section change (end of a chapter, new topic). Renders as <hr>, which AT announces.

HTML's <hr> is "a paragraph-level thematic break"; it has implicit role="separator" and screen readers announce "separator" or "horizontal rule". Reach for decorative={false} when the line carries meaning (between two chapters, between the body and footer of a long article).

Chapter 1 concludes.


Chapter 2 begins.

<p>Chapter 1 concludes.</p>
<Separator decorative={false} />
<p>Chapter 2 begins.</p>
<p>Chapter 1 concludes.</p>
{{ separator(decorative=false) }}
<p>Chapter 2 begins.</p>
<p>Chapter 1 concludes.</p>
{{template "separator" (dict "Decorative" (ptr false))}}
<p>Chapter 2 begins.</p>
<p>Chapter 1 concludes.</p>
<.separator decorative={false} />
<p>Chapter 2 begins.</p>
<div class="w-full max-w-md space-y-3 text-sm">
  <p>Chapter 1 concludes.</p>
  <hr data-slot="separator" data-orientation="horizontal" aria-orientation="horizontal" class="shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px border-0"/>
  <p>Chapter 2 begins.</p>
</div>

Vertical — inside a flex row

Set orientation="vertical". The parent needs a defined height (flex with items-center, or explicit h-*).

Vertical separators don't have a native HTML equivalent. When decorative we render a div; when semantic we add role="separator" + aria-orientation="vertical". APG notes that vertical orientation must be set explicitly — assistive tech can't infer it from CSS.

Profile
Settings
Log out
<div class="flex h-6 items-center gap-3 text-sm">
  <span>Profile</span>
  <Separator orientation="vertical" />
  <span>Settings</span>
  <Separator orientation="vertical" />
  <span>Log out</span>
</div>
<div class="flex h-6 items-center gap-3 text-sm">
  <span>Profile</span>  {{ separator(orientation="vertical") }}
  <span>Settings</span> {{ separator(orientation="vertical") }}
  <span>Log out</span>
</div>
<div class="flex h-6 items-center gap-3 text-sm">
  <span>Profile</span>  {{template "separator" (dict "Orientation" "vertical")}}
  <span>Settings</span> {{template "separator" (dict "Orientation" "vertical")}}
  <span>Log out</span>
</div>
<div class="flex h-6 items-center gap-3 text-sm">
  <span>Profile</span>  <.separator orientation="vertical" />
  <span>Settings</span> <.separator orientation="vertical" />
  <span>Log out</span>
</div>
<div class="flex h-6 items-center gap-3 text-sm">
  <span>Profile</span>
  <div data-slot="separator" data-orientation="vertical" class="shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px">
  </div>
  <span>Settings</span>
  <div data-slot="separator" data-orientation="vertical" class="shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px">
  </div>
  <span>Log out</span>
</div>

Further reading

API Reference

<Separator>

PropTypeDefaultDescription
orientation"horizontal"|"vertical""horizontal"
Direction. Vertical needs a parent with defined height.
decorativebooleantrue
Decorative — div with no role (AT skips). Semantic — <hr> for horizontal, div+role for vertical.MDNrole="separator"
classstring
Extra Tailwind classes appended to the root element.