Skip to content
Brok UI

Loading…

No results

Disclosure

Open source Core

Progressive disclosure — a stacked accordion, a single collapsible section and a clamp-and-reveal show-more.

Version
v1.1.1
Stability
stable
License
MIT
Related
Accordion
Collapsible

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.

previews.components.show-more.default.blade.php 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>

Installation

terminal
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:

resources/js/ui/index.js JS
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.

  • blade resources/views/components/ui/show-more.blade.php
  • js resources/js/ui/show-more.js
Registry dependencies
button separator
Packages
composer: jml/brok:^0.2
npm: alpinejs

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.

show-more.md
# 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

manifest knowledge + registry-derived coverage

Props

Props accepted by this component: name, type, default value and description.
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.

show-more show-more-content show-more-mask

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

Progressive disclosure

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
root show-more-content show-more-mask
Theming hooks
show-more show-more-content show-more-mask

Accessibility

WCAG 2.2 AA Keyboard focus-visible RTL-ready Localized labels Dark mode
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-slot attribute for styling and scripting hooks.
  • Focus-visible rings use the ring token, 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 under dir="rtl" — flip the preview to RTL to confirm.
  • Dark mode uses the same semantic tokens under the dark class; high contrast follows forced-color system tokens.

Livewire

Needs wire:key

Add a stable wire:key when Livewire can reorder this interactive component.

livewire-component.blade.php Blade
<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.

livewire-form.blade.php Blade
<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.

resources/views/components/ui/show-more.blade.php Blade
@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>
resources/js/ui/show-more.js JS
/**
 * 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