component — display
Show More
A hairline divider carrying an inline toggle pill (or plain label) to reveal collapsed content. Built on react-aria-components and styled with the quebi design system.
- divider
- toggle
- expand
- collapse
- interactive
05 examples
Default
A horizontal hairline with a centered toggle pill.
Controlled selection
Toggle the selected state to swap the label and reveal content.
First line is always visible.
As text
Render a mono count label instead of an interactive pill.
12 more replies
Vertical
A vertical rule with the toggle centered between two columns.
Left
Right
Disabled
source
show-more.tsxCopy this into your project. Resolve its dependencies from the registryDependencies in the component's API entry.
"use client"
import { composeRenderProps, ToggleButton } from "react-aria-components"
import { buttonStyles } from "@/components/button"
import { tv } from "@/lib/utils"
/**
* ShowMore — quebi design system
*
* A divider that carries a "show more" affordance: a pill toggle button (or
* plain label) centered on a hairline rule. Use it to gate collapsed content
* (long threads, extra results) behind a single inline control.
*
* The rule is a hairline. The pill's appearance is `buttonStyles` — not a
* copy of it (task #185): a hand transcription of an older Button hover once
* left a row of chips growing and glowing beside Buttons that did neither.
*/
const showMoreStyles = tv({
base: "text-sm leading-6 before:border-quebi-hairline after:border-quebi-hairline",
variants: {
orientation: {
vertical: "mx-1 h-auto self-stretch",
horizontal: "my-0.5 h-px w-full self-stretch",
},
},
compoundVariants: [
{
orientation: "vertical",
className:
"mx-2 flex flex-col items-center before:mb-2 before:flex-1 before:border-l after:mt-2 after:flex-1 after:border-r",
},
{
orientation: "horizontal",
className:
"my-2 flex items-center self-stretch before:me-2 before:flex-1 before:border-t after:ms-2 after:flex-1 after:border-t",
},
],
defaultVariants: {
orientation: "horizontal",
},
})
/**
* The pill, as a Button intent chosen by the toggle state.
*
* Resting is `outline`, expanded is `primary` — the slate action fill of a
* pressed button, not the signal of a selected row. Naming the intents instead of the classes keeps
* the chip tracking the recipe: hover, focus ring, disabled treatment and
* transition are whatever `Button` says they are.
*
* `size: "sm"` is the 38px control, and `isCircle` is the pill shape — the one
* thing a chip does not take from the default. Nothing here is a hand-rolled
* appearance class, which is the point: there is no copy left to drift.
*/
const showMorePillStyles = (isSelected: boolean) =>
buttonStyles({ intent: isSelected ? "primary" : "outline", size: "sm", isCircle: true })
interface ShowMoreProps extends Omit<React.ComponentProps<typeof ToggleButton>, "className"> {
className?: string
orientation?: "horizontal" | "vertical"
as?: "text" | "button"
text?: string
}
const ShowMore = ({
as = "button",
orientation = "horizontal",
className,
text,
...props
}: ShowMoreProps) => {
return (
<div className={showMoreStyles({ orientation, className })}>
{as === "button" ? (
<ToggleButton {...props} className={({ isSelected }) => showMorePillStyles(isSelected)}>
{composeRenderProps(props.children, (children) => children)}
</ToggleButton>
) : (
<span className="quebi-eyebrow">{text}</span>
)}
</div>
)
}
export type { ShowMoreProps }
export { ShowMore }