Skip to content
uiify

NativeDetails

Native disclosure: `<details>` root, `<summary>` trigger and a content body. 0 kB client JS, at most 0.4 kB gzipped.

Anatomy

<NativeDetails.Root name="faq">
  <NativeDetails.Summary>Shipping</NativeDetails.Summary>
  <NativeDetails.Content>Ships in 2-3 days.</NativeDetails.Content>
</NativeDetails.Root>

Live demo

This demo also runs with JavaScript disabled.

Shipping
Ships in 2-3 days.
Returns
30-day return window.

NativeDetails.Root

Renders <details>. Emits class="uiify-native-details", plus data-disabled="" when disabled.

PropTypeDefaultDescription
defaultOpenboolean—Applies the native `open` attribute on first render.
namestring—Groups sibling `<details name>` into an exclusive set. Must be unique per group and stable across SSR.
disabledboolean—Sets `data-disabled` on the root. Blocking the click itself is a CSS responsibility — see the CSS contract below.

NativeDetails.Summary

Renders <summary>. Emits aria-disabled="true" and tabIndex={-1} when disabled.

PropTypeDefaultDescription
disabledboolean—The browser has no native disabled state for `<summary>` — this loses focusability and gets `aria-disabled`.

NativeDetails.Content

Renders <div> (polymorphic via `as`/`render`). Emits consumer props only.

PropTypeDefaultDescription
asElementType"div"The disclosure body isn't always a `<div>` — e.g. an `<ol>` for a list.
renderRenderProp—Escape hatch for a custom element.

Rendered DOM

<details name="faq" class="uiify-native-details"><summary>Shipping</summary><div>Ships in 2-3 days.</div></details>

CSS contract

details[open] > summary::after {
  /* flip a disclosure marker, e.g. rotate an arrow */
}

.uiify-native-details[data-disabled] > summary {
  pointer-events: none; /* the element marks it disabled; blocking the click is your CSS */
}

Build your own

import { NativeDetails } from "@gunkin/uiify/elements/native-details";

export function Faq() {
  return (
    <NativeDetails.Root name="faq">
      <NativeDetails.Summary>
        What browsers are supported?
        <span aria-hidden="true" className="faq-chevron" />
      </NativeDetails.Summary>
      <NativeDetails.Content>
        Every evergreen browser, plus the last two Safari majors.
      </NativeDetails.Content>
    </NativeDetails.Root>
  );
}

// details[open] > summary .faq-chevron { transform: rotate(180deg); }

Used by

Gotchas

  • `name` groups sibling `<details>` into an exclusive, single-open set with zero JS — it must be unique per group and stable across SSR.
  • `disabled` sets `data-disabled`/`aria-disabled`/`tabIndex=-1`, but you still need `pointer-events: none` in your own CSS to actually block the click (see the CSS contract).
  • `NativeDetails.Content` is polymorphic (`as`/`render`) because the disclosure body isn't always a `<div>`.

Import

import { NativeDetails } from "@gunkin/uiify/elements/native-details";