Collapsible An interactive component that expands and collapses a panel.
React Native
Native source Markup CSS
Copy <div mlk-collapsible-root class="package-list">
<div class="package-list__row package-list__row--featured">
<span>@milkui/core/collapsible</span>
<span>Native primitive</span>
</div>
<a mlk-collapsible-trigger draggable="false" href="#packages-content">Show packages</a>
<div mlk-collapsible-content id="packages-content" hidden="until-found">
<div class="package-list__row">
<span>@milkui/react/collapsible</span>
<span>React adapter</span>
</div>
<div class="package-list__row">
<span>@milkui/core/primitive</span>
<span>Shared lifecycle</span>
</div>
</div>
</div>
<script type="module">
import { Root, Trigger, Content } from '@milkui/core/collapsible';
Root.define(document);
Trigger.define(document);
Content.define(document);
</script>Features Fragment links work before JavaScript loads. Controlled or uncontrolled open state. Semantic elements with no shadow DOM. React asChild composition, or native marker attributes. ARIA, data attributes, and keyboard behavior from shared primitive logic. CSS transitions. Anatomy Triggers link to content marked hidden="until-found" before JavaScript. Following a link reveals that panel without resetting others. Enhancement adds toggle and keyboard behavior.
Native anatomy Copy <div mlk-collapsible-root>
<a mlk-collapsible-trigger></a>
<div mlk-collapsible-content></div>
</div>API reference Root Prop Type Default Description defaultOpenbooleanfalseInitial uncontrolled open state. openbooleanundefinedControlled open state. onOpenChange(open: boolean) => voidundefinedState change request callback. disabledbooleanfalsePrevents trigger activation.
Trigger Prop Type Default Description disabledbooleanfalseDisables only this trigger. hrefstringContent fragmentLink to the content before enhancement.
Content No component-specific props.
Data attribute Values [data-open]Present when open; absent when closed [data-disabled]Present when disabled [mlk-collapsible-root]Root marker [mlk-collapsible-trigger]Trigger marker [mlk-collapsible-content]Content marker
CSS variable Description --mlk-collapsible-content-widthMeasured content width. --mlk-collapsible-content-heightMeasured content height.
Examples Controlled The primitive requests changes. Your state decides whether to accept them.
Native source Copy <script type="module">
import { Root, Trigger, Content } from '@milkui/core/collapsible';
Root.define(document);
Trigger.define(document);
Content.define(document);
await Promise.resolve();
let open = Root.get(document.getElementById('controlled-root')).open;
const root = Root.mount(document.getElementById('controlled-root'), {
open,
onOpenChange(nextOpen: boolean) {
if (accept.checked) setOpen(nextOpen);
},
});
function setOpen(nextOpen: boolean) {
open = nextOpen;
root.update({ open });
}
</script>CSS transitions Content stays mounted; CSS controls opening and closing motion.
Native source Copy <div mlk-collapsible-root id="animation-details">
<a mlk-collapsible-trigger draggable="false" href="#animation-details-content">Animation details</a>
<div mlk-collapsible-content id="animation-details-content" hidden="until-found">
<div class="demo__content-inner">Animated content</div>
</div>
</div>
<script type="module">
import { Root, Trigger, Content } from '@milkui/core/collapsible';
Root.define(document);
Trigger.define(document);
Content.define(document);
</script>Transition styles Copy .package-list {
width: min(100%, 520px);
margin: 0 auto;
padding: 18px;
border: 1px solid rgba(124, 58, 237, 0.16);
border-radius: var(--milk-radius, 14px);
background: rgba(255, 255, 255, 0.92);
}
.package-list__row {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
gap: 4px 18px;
padding: 13px 2px;
border-top: 1px solid var(--milk-line, #e8e1f2);
color: var(--milk-muted, #6d647d);
font-size: 14px;
}
.package-list__row--featured {
border-top: 0;
color: var(--milk-text, #241d33);
font-weight: 650;
}
[mlk-collapsible-content] > .package-list__row:first-child {
border-top: 0;
}
.package-list__row span:last-child {
color: var(--milk-muted, #6d647d);
font-weight: 400;
}
[mlk-collapsible-trigger] {
text-decoration: none;
display: flex;
width: 100%;
align-items: center;
justify-content: space-between;
gap: 16px;
margin-top: 8px;
padding: 12px 14px;
border: 1px solid #d8cef4;
border-radius: 10px;
color: var(--milk-text, #241d33);
background: white;
text-align: left;
cursor: pointer;
}
[mlk-collapsible-trigger]:hover:not(:disabled) {
background: #faf7ff;
text-decoration: none;
}
[mlk-collapsible-trigger]::after {
content: '+';
color: var(--milk-violet, #7c3aed);
font-size: 20px;
line-height: 1;
}
[mlk-collapsible-trigger][data-open]::after {
content: '−';
}
[mlk-collapsible-trigger]:disabled,
[mlk-collapsible-trigger][aria-disabled='true'] {
cursor: not-allowed;
opacity: 0.55;
}
[mlk-collapsible-content] > .demo__content-inner {
padding: 16px 2px 4px;
color: #564d66;
font-size: 14px;
line-height: 1.65;
}
[mlk-collapsible-content] {
scroll-margin-top: 4rem;
height: 0;
overflow: hidden;
content-visibility: hidden;
interpolate-size: allow-keywords;
transition:
height 220ms ease-in-out,
content-visibility 220ms allow-discrete;
}
[mlk-collapsible-content][data-open] {
height: auto;
content-visibility: visible;
}
/* Enable transitions once the primitive is interactive. */
:not([data-interactive]) > [mlk-collapsible-content] {
transition: none;
}
/* Before initialization, the browser reveals fragment targets by removing hidden. */
:not([data-interactive]) > [mlk-collapsible-content]:not([hidden]) {
height: auto;
content-visibility: visible;
}
:not([data-interactive]) > [mlk-collapsible-trigger]:has(~ [mlk-collapsible-content]:not([hidden]))::after {
content: '−';
}
Nested composition Nested roots resolve their nearest provider and stay isolated.
Native source Copy <div mlk-collapsible-root id="outer-details">
<a mlk-collapsible-trigger draggable="false" href="#outer-details-content">Outer details</a>
<div mlk-collapsible-content id="outer-details-content" hidden="until-found">
<div mlk-collapsible-root id="inner-details">
<a mlk-collapsible-trigger draggable="false" href="#inner-details-content">Inner details</a>
<div mlk-collapsible-content id="inner-details-content" hidden="until-found">Inner state is isolated.</div>
</div>
</div>
</div>Keyboard interactions Before enhancement, triggers are links to sections. Once enhanced, Enter and Space toggle them without changing the URL.
Key Description SpaceOpens or closes the collapsible. EnterOpens or closes the collapsible. TabMoves focus to the next focusable element.