What the primitive is for and when it earns its place.
Use it when the current workflow should stay in view
`reveal-ui` is for choices that need more than a label before the user can decide. The summary stays visible, the detail opens inline, and the surrounding workflow does not disappear.
- Inline editors that should not replace the summary they are editing.
- Card stacks where only one item should open at a time.
- Nested flows that need close propagation without building modal chains.
- Selection tasks where a label alone hides price, risk, timing, or caveats.
- A normal accordion already keeps enough context visible.
- The choice is actually a simple primitive value with no comparison burden.
- The UI needs a true modal because the surrounding app must become inert.
- The reveal content should unmount immediately with no phase-aware exit work.
The mental model is top, reveal, bottom
The component is deliberately shaped around three persistent regions instead of one summary block and one hidden panel.
Summary, heading, and trigger live here. This region stays mounted before, during, and after the reveal.
The inserted detail layer. It can be static content or a render function that reads the live reveal phase.
Footer actions, metrics, or summary context that should remain visible below the inserted detail.
Start with one panel and one trigger
This is the smallest useful integration surface for a production app. Start here, then add groups, scroll coordination, or more phase-aware motion as needed.
import * as React from 'react'
import {
RevealClose,
RevealPanel,
RevealTrigger,
useRevealPanelState,
} from 'reveal-ui'
export function AccountRevealCard() {
return (
<RevealPanel
keepMounted
magicMotion
restoreScrollOnClose
scrollOnOpen
content={({ phase }) => (
<div className="border-t border-slate-200 px-5 py-4">
<StatusInline phase={phase} />
<RevealClose className="mt-4 rounded-md border px-3 py-2 text-sm">
Done
</RevealClose>
</div>
)}
>
<RevealPanel.Top>
<div className="rounded-t-3xl border border-slate-200 bg-white px-5 py-4">
<RevealTrigger className="rounded-md bg-slate-950 px-4 py-2 text-sm text-white">
Edit inline
</RevealTrigger>
</div>
</RevealPanel.Top>
<RevealPanel.Bottom>
<div className="rounded-b-3xl border border-t-0 border-slate-200 bg-slate-50 px-5 py-4 text-sm">
Footer actions stay visible below the reveal.
</div>
</RevealPanel.Bottom>
</RevealPanel>
)
}
function StatusInline({ phase }: { phase: string }) {
const panel = useRevealPanelState()
return <p className="text-sm text-slate-700">Panel phase: {panel.phase ?? phase}</p>
}