Disclosure
Progressive disclosure — a stacked accordion, a single collapsible section and a clamp-and-reveal show-more.
Show More — A disclosure that clamps long content to a fixed height with a gradient fade mask, then reveals the rest with a Show more / Show less toggle.
Preview
About this release
This release focuses on refining the disclosure primitives so long-form content can be summarised without hiding it behind a separate page. The fade mask signals that more is available below the fold.
Click Show more to expand the full description. The collapsed height, fade colour and separator are all configurable, and the toggle is keyboard-operable with the expanded state announced to assistive technology.
Everything below the clamp is part of the same content region, so screen-reader users reach it in document order once expanded — no off-screen teleporting or duplicated markup.
<div class="mx-auto w-full max-w-md">
<x-ui.show-more collapsed-height="6rem">
<div class="space-y-3 text-sm text-muted-foreground">
<h3 class="text-base font-semibold text-foreground">{{ __('About this release') }}</h3>
<p>{{ __('This release focuses on refining the disclosure primitives so long-form content can be summarised without hiding it behind a separate page. The fade mask signals that more is available below the fold.') }}</p>
<p>{{ __('Click Show more to expand the full description. The collapsed height, fade colour and separator are all configurable, and the toggle is keyboard-operable with the expanded state announced to assistive technology.') }}</p>
<p>{{ __('Everything below the clamp is part of the same content region, so screen-reader users reach it in document order once expanded — no off-screen teleporting or duplicated markup.') }}</p>
</div>
</x-ui.show-more>
</div>
Installation
php artisan ui:add show-more
Note
This component ships an Alpine behavior module at
resources/js/ui/show-more.js. Import it once from your bundle so it registers on alpine:init:
import './show-more.js';
Registry contract
php artisan ui:add show-more
writes only the files below. The CLI validates each file hash before writing and asks before it replaces a local change, unless you pass --force.
-
resources/views/components/ui/show-more.blade.php -
resources/js/ui/show-more.js
Use with AI
A brief for your coding agent: install command, usage, props, guidance and the rules. Copy it, or open a prompt about this component in an assistant.
# Brok UI: Show More (`show-more`)
A disclosure that clamps long content to a fixed height with a gradient fade mask, then reveals the rest with a Show more / Show less toggle.
Brok UI is a Laravel Blade component registry. Installed components are plain Blade files the app owns.
## Install
```bash
php artisan ui:add show-more
```
## Usage
```blade
<div class="mx-auto w-full max-w-md">
<x-ui.show-more collapsed-height="6rem">
<div class="space-y-3 text-sm text-muted-foreground">
<h3 class="text-base font-semibold text-foreground">{{ __('About this release') }}</h3>
<p>{{ __('This release focuses on refining the disclosure primitives so long-form content can be summarised without hiding it behind a separate page. The fade mask signals that more is available below the fold.') }}</p>
<p>{{ __('Click Show more to expand the full description. The collapsed height, fade colour and separator are all configurable, and the toggle is keyboard-operable with the expanded state announced to assistive technology.') }}</p>
<p>{{ __('Everything below the clamp is part of the same content region, so screen-reader users reach it in document order once expanded — no off-screen teleporting or duplicated markup.') }}</p>
</div>
</x-ui.show-more>
</div>
```
## Props
- `expanded` (bool, default `false`) — Initial expanded state.
- `collapsedHeight` (string, default `8rem`) — Clamped height while collapsed, as a CSS length.
- `mask` (string, default `background`) — Gradient fade colour matching the surface behind it: background, card, muted, popover, accent, or sidebar.
- `separator` (bool, default `true`) — Renders a separator line between the content and the toggle button.
- `moreLabel` (string, default `Show more`) — Toggle label while collapsed.
- `lessLabel` (string, default `Show less`) — Toggle label while expanded.
## Use when
- Use for secondary or optional content, especially when users usually inspect one short section at a time.
- Truncating a long block of the same content, such as a long description or comment, to a fixed height with a way to reveal the rest.
- Wanting the cut-off edge to read as an intentional fade rather than an abrupt clip.
## Avoid when
- Do not hide required workflow content or comparison-heavy information because disclosure controls reduce visibility and increase interaction cost.
- The content is several distinct sections that should each toggle independently; use accordion or collapsible.
- The full content is short enough to always show; skip the clamp entirely.
## Anti-patterns
- Hiding required workflow content
- Using accordions for cross-section comparison
## Rules
- Use the `<brok:show-more>` tag (or `<x-ui.show-more>`) in Blade; do not rewrite the component.
- Prefer the documented props and variants over utility-class overrides; when a utility must win, use the `!` important modifier.
- Use semantic design tokens (`bg-primary`, `text-muted-foreground`), never raw colour utilities.
- Keep the `data-slot` attributes; they are the styling and test hooks.
## Links
- Docs: https://brokui.dev/docs/components/show-more
- Registry JSON (files, props, contract): https://brokui.dev/r/open/show-more.json
Working in Claude Code, Cursor or Codex? Give the agent the whole registry through the MCP server or the Brok UI skill.
API
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| expanded | bool | false | Initial expanded state. |
| collapsedHeight | string | 8rem | Clamped height while collapsed, as a CSS length. |
| mask | string | background | Gradient fade colour matching the surface behind it: background, card, muted, popover, accent, or sidebar. |
| separator | bool | true | Renders a separator line between the content and the toggle button. |
| moreLabel | string | Show more | Toggle label while collapsed. |
| lessLabel | string | Show less | Toggle label while expanded. |
Slots
default— Content clamped while collapsed.
Data slots
Stable hooks for CSS overrides and browser tests.
Behavior
- While collapsed, content is clamped to collapsedHeight and a gradient fade mask blends the cut-off edge into the chosen surface colour.
- Expanding animates the region to its measured content height before lifting the clamp entirely, and the chevron on the toggle rotates to match state.
- The clamp height is driven by a single CSS custom property rather than an inline arbitrary Tailwind class, so the value stays visible to Tailwind's JIT scanner.
- Installs a JavaScript behavior module when the registry item includes resources/js/ui files.
- Declares registry capability flags: a11y, interactive, responsive, rtl, darkMode, localized.
Guidance
Reveal optional or secondary content on demand.
Use when
- Use for secondary or optional content, especially when users usually inspect one short section at a time.
- Truncating a long block of the same content, such as a long description or comment, to a fixed height with a way to reveal the rest.
- Wanting the cut-off edge to read as an intentional fade rather than an abrupt clip.
Avoid when
- Do not hide required workflow content or comparison-heavy information because disclosure controls reduce visibility and increase interaction cost.
- The content is several distinct sections that should each toggle independently; use accordion or collapsible.
- The full content is short enough to always show; skip the clamp entirely.
Use instead
- Visible sections
- Tabs for a few long peer sections
Anti-patterns
- Hiding required workflow content
- Using accordions for cross-section comparison
- Anatomy
- Theming hooks
Accessibility
- Keyboard
- native
- Focus
native
- The toggle is a real button exposing aria-expanded and aria-controls, wired to the content region's id, so screen readers track the disclosure state.
- Height and chevron transitions respect prefers-reduced-motion.
- Semantic HTML and a stable
data-slotattribute for styling and scripting hooks. - Focus-visible rings use the
ringtoken, so keyboard focus is always visible. - Disabled and invalid states are conveyed to assistive tech, not by color alone.
- Targets WCAG 2.2 AA; verify contrast in light, dark, admin and customer surfaces in the preview.
- Labels go through
__()and layout uses logical properties (ms-*,text-start), so it mirrors underdir="rtl"— flip the preview to RTL to confirm. - Dark mode uses the same semantic tokens under the
darkclass; high contrast follows forced-color system tokens.
Livewire
Add a stable wire:key when Livewire can reorder this interactive component.
<div wire:key="show-more-{{ $record->id }}">
<div class="mx-auto w-full max-w-md">
<x-ui.show-more collapsed-height="6rem">
<div class="space-y-3 text-sm text-muted-foreground">
<h3 class="text-base font-semibold text-foreground">{{ __('About this release') }}</h3>
<p>{{ __('This release focuses on refining the disclosure primitives so long-form content can be summarised without hiding it behind a separate page. The fade mask signals that more is available below the fold.') }}</p>
<p>{{ __('Click Show more to expand the full description. The collapsed height, fade colour and separator are all configurable, and the toggle is keyboard-operable with the expanded state announced to assistive technology.') }}</p>
<p>{{ __('Everything below the clamp is part of the same content region, so screen-reader users reach it in document order once expanded — no off-screen teleporting or duplicated markup.') }}</p>
</div>
</x-ui.show-more>
</div>
</div>
Validation
Validation support: native. Keep the error message connected with aria-describedby.
<form wire:submit="save" class="space-y-2">
<brok:show-more
wire:model="value"
:aria-invalid="$errors->has('value') ? 'true' : 'false'"
aria-describedby="value-error"
/>
@error('value')
<p id="value-error" role="alert">{{ $message }}</p>
@enderror
<brok:button type="submit" wire:loading.attr="disabled">
<span wire:loading.remove>Save</span>
<span wire:loading>Saving…</span>
</brok:button>
</form>
Source
The exact, editable files ui:add writes
into your app. Previews render this same code; there are no preview-only components.
@props([
'expanded' => false,
'collapsedHeight' => '8rem',
'mask' => 'background',
'separator' => true,
'moreLabel' => 'Show more',
'lessLabel' => 'Show less',
])
{{--
Show More — a disclosure that clamps long content to a fixed height and
reveals the rest on demand. The reveal animates to the measured content
height (show-more.js) and then lifts the clamp. While collapsed, a gradient fade mask blends the
cut-off edge into the surface behind it (so the truncation reads as
intentional, not chopped); a Show more / Show less button toggles the state.
The clamp is one CSS custom property (`--show-more-max`) driven by the
behaviour module, so the value is data-driven without splicing a variable
into an arbitrary Tailwind class (which the JIT can't see). The mask colour is chosen from a
fixed `from-*` token map for the same reason — never a raw colour.
A11y: the toggle is a real <button> exposing aria-expanded and aria-controls,
wired to the content region's id via a scoped x-id so many instances on one
page stay independent. Height + chevron transitions are reduced-motion safe.
--}}
@php
// Fixed token classes only — keeps gradient colour out of inline styles and
// visible to the Tailwind JIT scanner.
$maskClass = match ($mask) {
'card' => 'from-card',
'muted' => 'from-muted',
'popover' => 'from-popover',
'accent' => 'from-accent',
'sidebar' => 'from-sidebar',
default => 'from-background',
};
@endphp
<div
x-data="uiShowMore({ expanded: @js((bool) $expanded), collapsed: @js($collapsedHeight) })"
x-id="['show-more-content']"
data-slot="show-more"
{{ $attributes->merge(['class' => 'flex flex-col']) }}
>
{{-- Clamped content region. Collapsed height comes from a CSS var; expanded
lifts the clamp. overflow-hidden keeps the fade mask reading cleanly. --}}
<div
x-ref="content"
:id="$id('show-more-content')"
data-slot="show-more-content"
class="relative max-h-[var(--show-more-max)] overflow-hidden transition-[max-height] duration-300 ease-out motion-reduce:transition-none"
:style="`--show-more-max: ${maxHeight}`"
style="--show-more-max: {{ $expanded ? 'none' : $collapsedHeight }};"
>
{{ $slot }}
{{-- Gradient fade mask — only visible while collapsed. --}}
<div
x-show="!expanded"
data-slot="show-more-mask"
aria-hidden="true"
class="pointer-events-none absolute inset-x-0 bottom-0 h-16 bg-gradient-to-t to-transparent {{ $maskClass }}"
></div>
</div>
@if ($separator)
<x-ui.separator class="mt-4" />
@endif
<div class="mt-3 flex justify-center">
<x-ui.button
type="button"
variant="ghost"
size="sm"
@click="toggle()"
x-bind:aria-expanded="expanded.toString()"
x-bind:aria-controls="$id('show-more-content')"
class="gap-2"
>
<span x-text="expanded ? @js(__($lessLabel)) : @js(__($moreLabel))">{{ __($moreLabel) }}</span>
{{-- Through the icon slot, not the label: the label span is
single-line, and the preflight `svg { display: block }` would
otherwise push a chevron in it onto its own line. --}}
<x-slot:iconTrailing>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="size-4 transition-transform duration-200 motion-reduce:transition-none"
:class="expanded ? 'rotate-180' : ''"
aria-hidden="true"
>
<path d="m6 9 6 6 6-6" />
</svg>
</x-slot:iconTrailing>
</x-ui.button>
</div>
</div>
/**
* Show More behaviour — the clamp/reveal disclosure.
*
* Holds the `expanded` flag and a `toggle()` action; the Blade binds the fade
* mask's visibility and the chevron rotation to it, and the content's clamp to
* `maxHeight` through one CSS variable. The reveal animates max-height to the
* content's *measured* height (never an arbitrary large value, which would make
* the open snap and the close lag), then lifts the clamp so later content
* growth is never clipped. The one timer is cleared on teardown.
*
* Self-registers on `alpine:init` so import order does not matter.
*/
document.addEventListener('alpine:init', () => {
window.Alpine.data('uiShowMore', (config = {}) => ({
expanded: config.expanded ?? false,
collapsed: config.collapsed ?? '8rem',
// The current clamp: the collapsed height, a measured pixel height while
// the transition runs, or `none` once an open has settled.
maxHeight: config.expanded ? 'none' : (config.collapsed ?? '8rem'),
_timer: null,
toggle() {
const content = this.$refs.content;
clearTimeout(this._timer);
if (this.expanded) {
// Give the transition a concrete start value (the clamp is lifted
// while open), then collapse on the next frame.
this.maxHeight = `${content.scrollHeight}px`;
this.expanded = false;
requestAnimationFrame(() => requestAnimationFrame(() => { this.maxHeight = this.collapsed; }));
return;
}
this.maxHeight = `${content.scrollHeight}px`;
this.expanded = true;
this._timer = setTimeout(() => { this.maxHeight = 'none'; }, 320);
},
destroy() {
clearTimeout(this._timer);
},
}));
});
Ownership & lifecycle
Owner, release state, review evidence and adoption for this item.
- Owner
- Platform UI (@JoshJML)
- Current version
-
1.1.1 - Status
- Stable
- License
-
open - Accessibility reviewed
- No review date recorded
- Last breaking change
- No date recorded
- Deprecation
- Not deprecated
- Contract
-
v6 - Foundation
-
≥ 1.0.0