Svelte Headless UI

Disclosure

Basic example

Disclosures are built using the Disclosure, DisclosureButton, and DisclosurePanel components.

Clicking the DisclosureButton will automatically open/close the DisclosurePanel, and all components will receive the appropriate aria-* attributes like aria-expanded and aria-controls.

<script>
  import {
    Disclosure,
    DisclosureButton,
    DisclosurePanel,
  } from "@rgossiaux/svelte-headlessui";
</script>

<Disclosure>
  <DisclosureButton>Is team pricing available?</DisclosureButton>

  <DisclosurePanel>
    Yes! You can purchase a license that you can share with your entire team.
  </DisclosurePanel>
</Disclosure>

Styling

See here for some general notes on styling the components in this library.

Open panels

Disclosure and its related components expose a slot prop containing the open state of the panel. You can use this to apply whatever styles you wish.

<script>
  import {
    Disclosure,
    DisclosureButton,
    DisclosurePanel,
  } from "@rgossiaux/svelte-headlessui";
  import { ChevronRightIcon } from "@rgossiaux/svelte-heroicons/solid";
</script>

<Disclosure let:open>
  <DisclosureButton>
    <span>Is team pricing available?</span>
    <!-- Use the `open` slot prop to rotate the icon when the panel is open -->
    <ChevronRightIcon style={open ? "transform: rotate(90deg);" : ""} />
  </DisclosureButton>

  <DisclosurePanel>
    Yes! You can purchase a license that you can share with your entire team.
  </DisclosurePanel>
</Disclosure>

Showing/hiding the panel

By default, your DisclosurePanel will be shown/hidden automatically based on the internal open state tracked within the Disclosure component itself.

If you’d rather handle this yourself (perhaps because you need to add an extra wrapper element for one reason or another), you can add a static prop to the DisclosurePanel component to tell it to always render, and use the open slot prop to show or hide the panel yourself.

<script>
  import {
    Disclosure,
    DisclosureButton,
    DisclosurePanel,
  } from "@rgossiaux/svelte-headlessui";
</script>

<Disclosure let:open>
  <DisclosureButton>Is team pricing available?</DisclosureButton>

  {#if open}
    <div>
      <!-- Using `static`, `DisclosurePanel` is always rendered,
            and ignores the `open` state -->
      <DisclosurePanel static>
        Yes! You can purchase a license that you can share with your entire
        team.
      </DisclosurePanel>
    </div>
  {/if}
</Disclosure>

Closing disclosures manually

To close a disclosure manually when clicking a child of its panel, render that child as a DisclosureButton. You can use the as prop to customize which element is being rendered.

<script>
  import {
    Disclosure,
    DisclosureButton,
    DisclosurePanel,
  } from "@rgossiaux/svelte-headlessui";
</script>

<Disclosure>
  <DisclosureButton>Open mobile menu</DisclosureButton>
  <DisclosurePanel>
    <DisclosureButton as="a" href="/home">Home</DisclosureButton>
    <!-- ... -->
  </DisclosurePanel>
</Disclosure>

This is especially useful when using disclosures for things like mobile menus that contain links, where you want the disclosure to close when navigating to the next page.

Alternatively, Disclosure and DisclosurePanel expose a close() slot prop which you can use to imperatively close the panel:

<script>
  import {
    Disclosure,
    DisclosureButton,
    DisclosurePanel,
  } from "@rgossiaux/svelte-headlessui";
</script>

<Disclosure>
  <DisclosureButton>Solutions</DisclosureButton>
  <DisclosurePanel let:close>
    <button
      on:click={async () => {
        await fetch("/accept-terms", { method: "POST" });
        close();
      }}
    >
      Read and accept
    </button>
    <!-- ... -->
  </DisclosurePanel>
</Disclosure>

By default the DisclosureButton receives focus after calling close(), but you can change this by passing an element into close(el).

Transitions

To animate the opening and closing of the disclosure panel, you can use this library’s Transition component or Svelte’s built-in transition engine. See that page for a comparison.

Using the Transition component

To use the Transition component, all you need to do is wrap the DisclosurePanel in a <Transition> and the panel will transition automatically.

<script>
  import {
    Disclosure,
    DisclosureButton,
    DisclosurePanel,
    Transition,
  } from "@rgossiaux/svelte-headlessui";
</script>

<Disclosure>
  <DisclosureButton>Is team pricing available?</DisclosureButton>
  <!-- This example uses Tailwind's transition classes -->
  <Transition
    enter="transition duration-100 ease-out"
    enterFrom="transform scale-95 opacity-0"
    enterTo="transform scale-100 opacity-100"
    leave="transition duration-75 ease-out"
    leaveFrom="transform scale-100 opacity-100"
    leaveTo="transform scale-95 opacity-0"
  >
    <DisclosurePanel>
      <!-- ... -->
    </DisclosurePanel>
  </Transition>
</Disclosure>

The components in this library communicate with each other, so the Transition will be managed automatically when the Listbox is opened/closed. If you require more control over this behavior, you may use a more explicit version:

<script>
  import {
    Disclosure,
    DisclosureButton,
    DisclosurePanel,
    Transition,
  } from "@rgossiaux/svelte-headlessui";
</script>

<Disclosure let:open>
  <DisclosureButton>Is team pricing available?</DisclosureButton>
  <!-- This example uses Tailwind's transition classes -->
  <Transition
    show={open}
    enter="transition duration-100 ease-out"
    enterFrom="transform scale-95 opacity-0"
    enterTo="transform scale-100 opacity-100"
    leave="transition duration-75 ease-out"
    leaveFrom="transform scale-100 opacity-100"
    leaveTo="transform scale-95 opacity-0"
  >
    <!-- When controlling the transition manually, make sure to use `static` -->
    <DisclosurePanel static>
      <!-- ... -->
    </DisclosurePanel>
  </Transition>
</Disclosure>

Using Svelte transitions

The last example above also provides a blueprint for using Svelte transitions:

<script>
  import {
    Disclosure,
    DisclosureButton,
    DisclosurePanel,
  } from "@rgossiaux/svelte-headlessui";
  import { fade } from "svelte/transition";
</script>

<Disclosure let:open>
  <DisclosureButton>Is team pricing available?</DisclosureButton>
  {#if open}
    <div transition:fade>
      <!-- When controlling the transition manually, make sure to use `static` -->
      <DisclosurePanel static>
        <!-- ... -->
      </DisclosurePanel>
    </div>
  {/if}
</Disclosure>

Make sure to use the static prop, or else the exit transitions won’t work correctly.

Accessibility notes

Mouse interaction

Clicking a DisclosureButton toggles the disclosure’s panel open and closed.

Keyboard interaction

Command Description
<Enter> / <Space> when a DisclosureButton is focused Toggles panel

Other

All relevant ARIA attributes are automatically managed.

For a full reference on all accessibility features implemented in Disclosure, see the ARIA spec on Disclosure.

Component API

Disclosure

The main disclosure component.

Prop Default Type Description
as div string The element the Disclosure should render as
defaultOpen false boolean Whether the Disclosure should be open by default
Slot prop Type Description
open boolean Whether the disclosure is open
close (el?: HTMLElement) => void Closes the disclosure and focuses el, if passed, or the DisclosureButton if not

DisclosureButton

This is the trigger button to toggle a disclosure.

You can also use this DisclosureButton component inside a DisclosurePanel. If you do, it will behave as a close button and have the appropriate aria-* attributes.

Prop Default Type Description
as button string The element the DisclosureButton should render as
Slot prop Type Description
open boolean Whether or not the disclosure is open

DisclosurePanel

This component contains the contents of your disclosure.

Prop Default Type Description
as div string The element the DisclosurePanel should render as
static false boolean Whether the element should ignore the internally managed open/closed state
unmount true boolean Whether the element should be unmounted, instead of just hidden, based on the open/closed state

Note that static and unmount cannot be used together.

Slot prop Type Description
open boolean Whether or not the disclosure is open
close (el?: HTMLElement) => void Closes the disclosure and focuses el, if passed, or the DisclosureButton if not