Skip to main content
On this pageOverview

Disclosure

Overview

A toggle for showing and hiding content inline. Disclosure manages its own open/closed state and renders a button + panel pair. Use it for FAQs, accordions, and collapsible sections. For overlaying content in a floating panel, use Dialog or Popover instead.

For programmatic control in update functions, use Disclosure.toggle(model) and Disclosure.close(model) which return [Model, Commands] directly.

See it in an app

Check out how Disclosure is wired up in a real Foldkit app.

Examples

Pass buttonContent and panelContent directly — Disclosure handles the wrapper, ARIA linking, and toggle behavior. Style the button and panel with className or attributes props.

// Pseudocode walkthrough of the Foldkit integration points. Each labeled
// block below is an excerpt — fit them into your own Model, init, Message,
// update, and view definitions.
import { Effect } from 'effect'
import { Command, Ui } from 'foldkit'
import { m } from 'foldkit/message'
import { evo } from 'foldkit/struct'

import { p, span } from './html'

// Add a field to your Model for the Disclosure Submodel:
const Model = S.Struct({
  disclosure: Ui.Disclosure.Model,
  // ...your other fields
})

// In your init function, initialize the Disclosure Submodel with a unique id:
const init = () => [
  {
    disclosure: Ui.Disclosure.init({ id: 'faq-1' }),
    // ...your other fields
  },
  [],
]

// Embed the Disclosure Message in your parent Message:
const GotDisclosureMessage = m('GotDisclosureMessage', {
  message: Ui.Disclosure.Message,
})

// Inside your update function's M.tagsExhaustive({...}), delegate to Disclosure.update:
GotDisclosureMessage: ({ message }) => {
  const [nextDisclosure, commands] = Ui.Disclosure.update(
    model.disclosure,
    message,
  )

  return [
    // Merge the next state into your Model:
    evo(model, { disclosure: () => nextDisclosure }),
    // Forward the Submodel's Commands through your parent Message:
    commands.map(
      Command.mapEffect(
        Effect.map(message => GotDisclosureMessage({ message })),
      ),
    ),
  ]
}

// Inside your view function, render the disclosure:
Ui.Disclosure.view({
  model: model.disclosure,
  toParentMessage: message => GotDisclosureMessage({ message }),
  buttonContent: span([], ['What is Foldkit?']),
  panelContent: p([], ['A functional UI framework built on Effect-TS.']),
  buttonClassName:
    'flex items-center justify-between w-full p-4 border rounded-lg data-[open]:rounded-b-none',
  panelClassName: 'p-4 border-x border-b rounded-b-lg',
})

Styling

Use the data-open attribute to style the button and panel differently when open. A common pattern is rotating a chevron icon and changing border radius: data-[open]:rounded-b-none on the button, rounded-b-lg on the panel.

AttributeCondition
data-openPresent on both button and panel when the disclosure is open.
data-disabledPresent on the button when isDisabled is true.

Keyboard Interaction

KeyDescription
EnterToggles the disclosure.
SpaceToggles the disclosure.

Accessibility

The toggle button receives aria-expanded and aria-controls linking to the panel. When the disclosure closes, focus is returned to the toggle button automatically.

API Reference

InitConfig

Configuration object passed to Disclosure.init().

NameTypeDefaultDescription
idstring-Unique ID for the disclosure instance.
isOpenbooleanfalseInitial open/closed state.

ViewConfig

Configuration object passed to Disclosure.view().

NameTypeDefaultDescription
modelDisclosure.Model-The disclosure state from your parent Model.
toParentMessage(childMessage: Disclosure.Message) => ParentMessage-Wraps Disclosure Messages in your parent Message type for Submodel delegation.
buttonContentHtml-Content rendered inside the toggle button.
panelContentHtml-Content rendered inside the disclosure panel.
buttonClassNamestring-CSS class for the toggle button.
buttonAttributesReadonlyArray<Attribute<Message>>-Additional attributes for the toggle button.
panelClassNamestring-CSS class for the panel.
panelAttributesReadonlyArray<Attribute<Message>>-Additional attributes for the panel.
isDisabledbooleanfalseWhether the toggle button is disabled.
persistPanelbooleanfalseWhen true, keeps the panel in the DOM when closed (with the hidden attribute) instead of removing it.
onToggled() => Message-Optional domain-event handler fired when toggled, as an alternative to Submodel delegation.
buttonElementTagName'button'The HTML element to use for the toggle.
panelElementTagName'div'The HTML element to use for the panel.
classNamestring-CSS class for the wrapper element.
attributesReadonlyArray<Attribute<Message>>-Additional attributes for the wrapper element.

Stay in the update loop.

New releases, patterns, and the occasional deep dive.


Built with Foldkit.

© 2026 Devin Jameson