Skip to content
Brok UI

Loading…

No results

Changelog

Open source

Changelog / release-notes sections — a version-rail timeline, a card grid, and a date-rail timeline with per-entry media.

Preview

resources/views/blocks/changelog-version-rail.blade.php Blade
@props([
    'eyebrow' => 'Changelog',
    'heading' => 'What\'s new',
    'lead'    => 'Follow along as we ship improvements, fixes, and new features every week.',
    'entries' => null,
    // Opt-in client-side filter: a search field above the timeline narrows the
    // list to the changes whose text matches. Off by default (static markup).
    'searchable' => false,
    'searchPlaceholder' => 'Filter by item, prop or keyword…',
])

@php
    // Each entry is one release. Two shapes are supported:
    //
    // 1. A single category per release (the original shape): `category`,
    //    `variant`, `title`, `body` and a flat `changes` list.
    // 2. Typed sections per release (opt-in): `sections` — a list of
    //    ['title' => 'Added', 'variant' => 'soft-success', 'intro' => null,
    //    'changes' => [...]] groups, rendered as badge + list, the shape a real
    //    Keep-a-Changelog release has. `title`/`body`/`category` become optional.
    //
    // Pass an empty string for `eyebrow`, `heading` and `lead` to drop the
    // section header when the block sits under a page's own title.
    //
    // Optional keys on either shape: `id` (deep-link anchor for the release,
    // defaults to a slug of the version) and `dateLabel` (defaults to `date`).
    // A change may be a plain string (translated and escaped) or an Htmlable
    // (rendered as-is — for release notes the app already converted from
    // trusted Markdown).
    $entries ??= [
        [
            'version' => 'v2.4.0', 'date' => '2026-06-10', 'dateLabel' => 'June 10, 2026',
            'category' => 'Added', 'variant' => 'success',
            'title' => 'Changelog block family & timeline layout',
            'body' => 'Introduced a dedicated changelog section with a sticky version rail on large screens and a fully responsive stacked layout on mobile.',
            'changes' => ['New changelog block family with a timeline layout.', 'Left-rail version and date column becomes sticky at large breakpoints.', 'Category tags use semantic badge variants.', 'Full RTL support via logical-property utilities.'],
        ],
        [
            'version' => 'v2.3.2', 'date' => '2026-05-28', 'dateLabel' => 'May 28, 2026',
            'category' => 'Fixed', 'variant' => 'destructive',
            'title' => 'Badge contrast & separator RTL fixes',
            'body' => 'Resolved a pair of accessibility regressions: the destructive badge now meets WCAG 2.2 AA contrast, and the separator rendered on the wrong side in RTL documents.',
            'changes' => ['Destructive badge foreground adjusted to meet a 4.5:1 contrast ratio.', 'Separator logical-property side restored for RTL layouts.'],
        ],
        [
            'version' => 'v2.3.0', 'date' => '2026-05-14', 'dateLabel' => 'May 14, 2026',
            'category' => 'Improved', 'variant' => 'secondary',
            'title' => 'Badge sizes & dark-mode polish',
            'body' => 'Bumped the badge to v1.1.0 with an expanded size scale and unified token coverage. Dark-mode palettes were reviewed and tightened across the library.',
            'changes' => ['Badge gains an expanded size scale via the shared styles map.', 'All badge variants use semantic tokens exclusively.', 'Dark-mode muted and accent surfaces re-mapped.'],
        ],
    ];

    $renderChange = static fn ($change) => $change instanceof \Illuminate\Contracts\Support\Htmlable ? $change : __($change);
@endphp

<section
    data-slot="changelog"
    @if ($searchable)
        x-data="{
            query: '',
            matches: null,
            filter() {
                const q = this.query.trim().toLowerCase();
                let total = 0;
                const releases = Array.from(this.$root.querySelectorAll('[data-slot=changelog-release]'));
                releases.forEach((release) => {
                    const sectioned = release.querySelector('[data-slot=changelog-section]') !== null;
                    let visible = 0;
                    release.querySelectorAll('[data-slot=changelog-change]').forEach((change) => {
                        const haystack = (sectioned ? change : release).textContent.toLowerCase();
                        const hit = q === '' || haystack.includes(q);
                        change.hidden = ! hit;
                        if (hit) visible++;
                    });
                    release.querySelectorAll('[data-slot=changelog-section]').forEach((section) => {
                        const shownInSection = section.querySelectorAll('[data-slot=changelog-change]:not([hidden])').length;
                        section.hidden = shownInSection === 0;
                        const count = section.querySelector('[data-slot=changelog-count]');
                        if (count) count.textContent = shownInSection;
                    });
                    release.hidden = visible === 0;
                    total += visible;
                });
                // A divider shows only between two visible releases.
                const shown = releases.filter((release) => ! release.hidden);
                this.$root.querySelectorAll('[data-slot=changelog-divider]').forEach((divider) => {
                    const before = divider.previousElementSibling;
                    divider.hidden = ! before || before.hidden || shown.indexOf(before) === shown.length - 1;
                });
                this.matches = q === '' ? null : total;
            },
        }"
        x-init="$watch('query', () => filter())"
    @endif
    {{ $attributes->merge(['class' => 'bg-background py-16 text-foreground sm:py-24']) }}
>
    <div class="mx-auto max-w-3xl px-4">
        @if ($eyebrow || $heading || $lead)
            <div class="mb-12 text-center sm:mb-16">
                @if ($eyebrow)
                    <x-ui.badge variant="outline" class="mb-4">{{ __($eyebrow) }}</x-ui.badge>
                @endif
                @if ($heading)
                    <h2 class="mt-2 text-3xl font-bold tracking-tight sm:text-4xl">{{ __($heading) }}</h2>
                @endif
                @if ($lead)
                    <p class="mt-4 text-base text-muted-foreground sm:text-lg">{{ __($lead) }}</p>
                @endif
            </div>
        @endif

        @if ($searchable)
            <div data-slot="changelog-search" class="mb-6">
                <x-ui.input type="search" autocomplete="off" x-model.debounce.120ms="query" :placeholder="__($searchPlaceholder)" :aria-label="__('Filter changes')" />
                <p class="mt-2 min-h-6 text-sm text-muted-foreground" role="status" aria-live="polite" x-text="matches === null ? '' : (matches === 0 ? {{ Js::from(__('No changes match.')) }} : {{ Js::from(__(':count matching')) }}.replace(':count', matches))"></p>
            </div>
        @endif

        <ol class="relative" aria-label="{{ __('Release history') }}">
            @foreach ($entries as $index => $entry)
                @php
                    $sections = $entry['sections'] ?? null;
                    $anchor = $entry['id'] ?? \Illuminate\Support\Str::slug((string) $entry['version']);
                @endphp
                @if ($index > 0)
                    <li aria-hidden="true" data-slot="changelog-divider" class="my-10"><x-ui.separator /></li>
                @endif
                {{-- In sections mode the release is a real heading (h2 → the
                     sections' h3), carries the anchor, and its rail stays in view
                     while a long release scrolls; the single-category shape keeps
                     its version badge. --}}
                <li @if ($sections === null) id="{{ $anchor }}" @endif data-slot="changelog-release" class="flex scroll-mt-24 flex-col gap-6 lg:flex-row lg:gap-10">
                    <div class="flex shrink-0 flex-row flex-wrap items-center gap-4 lg:sticky lg:top-24 lg:w-40 lg:flex-col lg:items-start lg:gap-2 lg:self-start lg:pt-2">
                        <span aria-hidden="true" class="mt-2 hidden size-2 shrink-0 rounded-full bg-primary ring-4 ring-background lg:block"></span>
                        @if ($sections !== null)
                            <h2 id="{{ $anchor }}" class="scroll-mt-24 text-base font-semibold leading-snug"><a href="#{{ $anchor }}" class="hover:underline">{{ $entry['version'] }}</a></h2>
                        @else
                            <x-ui.badge as="a" href="#{{ $anchor }}" variant="outline" size="sm" class="shrink-0">{{ $entry['version'] }}</x-ui.badge>
                        @endif
                        @if (! empty($entry['date']))
                            <time datetime="{{ $entry['date'] }}" class="whitespace-nowrap text-sm text-muted-foreground">{{ __($entry['dateLabel'] ?? $entry['date']) }}</time>
                        @elseif (! empty($entry['dateLabel']))
                            <span class="min-w-0 text-sm text-muted-foreground">{{ __($entry['dateLabel']) }}</span>
                        @endif
                    </div>

                    <div class="min-w-0 flex-1 space-y-4 border-s-2 border-border ps-6 lg:border-s-0 lg:ps-0">
                        @if (! empty($entry['category']) || ! empty($entry['title']))
                            <div class="flex flex-wrap items-center gap-2">
                                @if (! empty($entry['category']))
                                    <x-ui.badge :variant="$entry['variant'] ?? 'secondary'" size="sm">{{ __($entry['category']) }}</x-ui.badge>
                                @endif
                                @if (! empty($entry['title']))
                                    <h3 class="text-base font-semibold leading-snug">{{ __($entry['title']) }}</h3>
                                @endif
                            </div>
                        @endif
                        @if (! empty($entry['body']))
                            <p class="text-sm leading-relaxed text-muted-foreground">{{ __($entry['body']) }}</p>
                        @endif

                        @if ($sections !== null)
                            <div class="space-y-8">
                                @foreach ($sections as $section)
                                    <section data-slot="changelog-section">
                                        <div class="flex items-center gap-2 text-sm">
                                            <h3 class="font-semibold"><x-ui.badge :variant="$section['variant'] ?? 'secondary'" size="sm">{{ __($section['title']) }}</x-ui.badge></h3>
                                            <span data-slot="changelog-count" class="text-muted-foreground">{{ count($section['changes'] ?? []) }}</span>
                                        </div>
                                        @if (! empty($section['intro']))
                                            <div class="prose prose-sm mt-4 max-w-none text-foreground">{{ $renderChange($section['intro']) }}</div>
                                        @endif
                                        @if (! empty($section['changes']))
                                            <ul role="list" class="mt-4 space-y-2">
                                                @foreach ($section['changes'] as $change)
                                                    <li data-slot="changelog-change" class="flex items-start gap-2 text-sm text-muted-foreground">
                                                        <span aria-hidden="true" class="mt-2 size-2 shrink-0 rounded-full bg-primary"></span>
                                                        <span class="min-w-0 [&_a]:text-foreground [&_a]:underline-offset-4 [&_a:hover]:underline [&_code]:rounded-sm [&_code]:bg-muted [&_code]:px-0.5 [&_code]:font-mono [&_code]:text-foreground [&_strong]:font-semibold [&_strong]:text-foreground">{{ $renderChange($change) }}</span>
                                                    </li>
                                                @endforeach
                                            </ul>
                                        @endif
                                    </section>
                                @endforeach
                            </div>
                        @elseif (! empty($entry['changes']))
                            <ul role="list" class="space-y-2">
                                @foreach ($entry['changes'] as $change)
                                    <li data-slot="changelog-change" class="flex items-start gap-2 text-sm text-muted-foreground">
                                        <span aria-hidden="true" class="mt-2 size-2 shrink-0 rounded-full bg-primary"></span>
                                        <span class="min-w-0">{{ $renderChange($change) }}</span>
                                    </li>
                                @endforeach
                            </ul>
                        @endif
                    </div>
                </li>
            @endforeach
        </ol>
    </div>
</section>

Installation

terminal
php artisan ui:add blocks/changelog-version-rail

Installs these dependencies automatically:

Registry contract

Install confidence

php artisan ui:add blocks/changelog-version-rail writes only the generated targets below. The CLI validates each file hash before writing and prompts before replacing local changes unless --force is used.

Version
1.1.2
License
open
Stability
stable
Contract
v1
Foundation
≥ 1.0.0
Type Generated target
blade resources/views/blocks/changelog-version-rail.blade.php

Registry dependencies

badge separator input

Package dependencies

composer: jml/brok:^0.2

npm: alpinejs

Use with AI

A brief for your coding agent: what the block is, the install command, how to render it, its props and the rules. Copy it, or open a prompt about this block in an assistant.

changelog-version-rail.md
# Brok UI block: Changelog (`changelog-version-rail`)

A changelog/release-notes timeline with a sticky version and date rail beside each release. A release carries one category tag, title, summary and bullet list, or typed sections (Added, Changed, Fixed, …) each with its own list; an opt-in search field filters the changes client-side.

Brok UI is a Laravel Blade registry. `ui:add` copies this block into the app as plain Blade the app owns; it composes installed `<brok:*>` primitives and semantic design tokens.

## Install

```bash
php artisan ui:add blocks/changelog-version-rail
```

## Render it

```blade
<x-blocks.changelog-version-rail />
```

## Props

- `eyebrow` (string, default `Changelog`)
- `heading` (string, default `What\'s new`)
- `lead` (string, default `Follow along as we ship improvements, fixes, and new features every week.`)
- `entries` (mixed|null, default `null`)
- `searchable` (bool, default `false`)
- `search-placeholder` (string, default `Filter by item, prop or keyword…`)

## Use when

- Publishing a release-notes timeline with a sticky version/date rail, where each release carries a category tag and change list.
- Supporting typed sections per release (Added, Changed, Fixed) as an opt-in shape alongside the simpler single-category release.

## Avoid when

- Use changelog-cards for a card-grid changelog or changelog-date-rail for a date-marker timeline with images instead.

## Rules

- Render the installed block with `<x-blocks.changelog-version-rail />` and pass data through its props; edit the copied file only for structural changes.
- Keep the semantic design tokens (`bg-background`, `text-muted-foreground`); never swap in raw colour utilities.
- Keep the `data-slot` attributes and the logical (start/end) spacing so the markup still mirrors under `dir="rtl"`.

## Links

- Docs: https://brokui.dev/blocks/changelog-version-rail
- Registry JSON (files, props, contract): https://brokui.dev/r/open/blocks/changelog-version-rail.json

Working in Claude Code, Cursor or Codex? Give the agent the whole registry through the MCP server or the Brok UI skill.

Guidance

Use when

  • Publishing a release-notes timeline with a sticky version/date rail, where each release carries a category tag and change list.
  • Supporting typed sections per release (Added, Changed, Fixed) as an opt-in shape alongside the simpler single-category release.

Avoid when

  • Use changelog-cards for a card-grid changelog or changelog-date-rail for a date-marker timeline with images instead.

Anti-patterns

  • Do not replace semantic props with conflicting utility classes.
  • Do not remove labels, focus styles, or state attributes.
Anatomy
root changelog changelog-search changelog-divider changelog-release changelog-section changelog-count changelog-change

Usage

Render the block as a component, passing data where useful:

resources/views/welcome.blade.php Blade
<x-blocks.changelog-version-rail />

Built from primitives

Composes <brok:*> Semantic tokens Re-themeable RTL-ready

This block composes installed <brok:*> primitives and semantic design tokens only — it does not reimplement any primitive. Re-theme it (light, dark, admin, customer) by editing CSS variables; flip the preview to RTL to confirm it mirrors.

Source

The exact, editable file ui:add writes into your app. The preview above renders this same source — there are no preview-only blocks.

resources/views/blocks/changelog-version-rail.blade.php Blade
@props([
    'eyebrow' => 'Changelog',
    'heading' => 'What\'s new',
    'lead'    => 'Follow along as we ship improvements, fixes, and new features every week.',
    'entries' => null,
    // Opt-in client-side filter: a search field above the timeline narrows the
    // list to the changes whose text matches. Off by default (static markup).
    'searchable' => false,
    'searchPlaceholder' => 'Filter by item, prop or keyword…',
])

@php
    // Each entry is one release. Two shapes are supported:
    //
    // 1. A single category per release (the original shape): `category`,
    //    `variant`, `title`, `body` and a flat `changes` list.
    // 2. Typed sections per release (opt-in): `sections` — a list of
    //    ['title' => 'Added', 'variant' => 'soft-success', 'intro' => null,
    //    'changes' => [...]] groups, rendered as badge + list, the shape a real
    //    Keep-a-Changelog release has. `title`/`body`/`category` become optional.
    //
    // Pass an empty string for `eyebrow`, `heading` and `lead` to drop the
    // section header when the block sits under a page's own title.
    //
    // Optional keys on either shape: `id` (deep-link anchor for the release,
    // defaults to a slug of the version) and `dateLabel` (defaults to `date`).
    // A change may be a plain string (translated and escaped) or an Htmlable
    // (rendered as-is — for release notes the app already converted from
    // trusted Markdown).
    $entries ??= [
        [
            'version' => 'v2.4.0', 'date' => '2026-06-10', 'dateLabel' => 'June 10, 2026',
            'category' => 'Added', 'variant' => 'success',
            'title' => 'Changelog block family & timeline layout',
            'body' => 'Introduced a dedicated changelog section with a sticky version rail on large screens and a fully responsive stacked layout on mobile.',
            'changes' => ['New changelog block family with a timeline layout.', 'Left-rail version and date column becomes sticky at large breakpoints.', 'Category tags use semantic badge variants.', 'Full RTL support via logical-property utilities.'],
        ],
        [
            'version' => 'v2.3.2', 'date' => '2026-05-28', 'dateLabel' => 'May 28, 2026',
            'category' => 'Fixed', 'variant' => 'destructive',
            'title' => 'Badge contrast & separator RTL fixes',
            'body' => 'Resolved a pair of accessibility regressions: the destructive badge now meets WCAG 2.2 AA contrast, and the separator rendered on the wrong side in RTL documents.',
            'changes' => ['Destructive badge foreground adjusted to meet a 4.5:1 contrast ratio.', 'Separator logical-property side restored for RTL layouts.'],
        ],
        [
            'version' => 'v2.3.0', 'date' => '2026-05-14', 'dateLabel' => 'May 14, 2026',
            'category' => 'Improved', 'variant' => 'secondary',
            'title' => 'Badge sizes & dark-mode polish',
            'body' => 'Bumped the badge to v1.1.0 with an expanded size scale and unified token coverage. Dark-mode palettes were reviewed and tightened across the library.',
            'changes' => ['Badge gains an expanded size scale via the shared styles map.', 'All badge variants use semantic tokens exclusively.', 'Dark-mode muted and accent surfaces re-mapped.'],
        ],
    ];

    $renderChange = static fn ($change) => $change instanceof \Illuminate\Contracts\Support\Htmlable ? $change : __($change);
@endphp

<section
    data-slot="changelog"
    @if ($searchable)
        x-data="{
            query: '',
            matches: null,
            filter() {
                const q = this.query.trim().toLowerCase();
                let total = 0;
                const releases = Array.from(this.$root.querySelectorAll('[data-slot=changelog-release]'));
                releases.forEach((release) => {
                    const sectioned = release.querySelector('[data-slot=changelog-section]') !== null;
                    let visible = 0;
                    release.querySelectorAll('[data-slot=changelog-change]').forEach((change) => {
                        const haystack = (sectioned ? change : release).textContent.toLowerCase();
                        const hit = q === '' || haystack.includes(q);
                        change.hidden = ! hit;
                        if (hit) visible++;
                    });
                    release.querySelectorAll('[data-slot=changelog-section]').forEach((section) => {
                        const shownInSection = section.querySelectorAll('[data-slot=changelog-change]:not([hidden])').length;
                        section.hidden = shownInSection === 0;
                        const count = section.querySelector('[data-slot=changelog-count]');
                        if (count) count.textContent = shownInSection;
                    });
                    release.hidden = visible === 0;
                    total += visible;
                });
                // A divider shows only between two visible releases.
                const shown = releases.filter((release) => ! release.hidden);
                this.$root.querySelectorAll('[data-slot=changelog-divider]').forEach((divider) => {
                    const before = divider.previousElementSibling;
                    divider.hidden = ! before || before.hidden || shown.indexOf(before) === shown.length - 1;
                });
                this.matches = q === '' ? null : total;
            },
        }"
        x-init="$watch('query', () => filter())"
    @endif
    {{ $attributes->merge(['class' => 'bg-background py-16 text-foreground sm:py-24']) }}
>
    <div class="mx-auto max-w-3xl px-4">
        @if ($eyebrow || $heading || $lead)
            <div class="mb-12 text-center sm:mb-16">
                @if ($eyebrow)
                    <x-ui.badge variant="outline" class="mb-4">{{ __($eyebrow) }}</x-ui.badge>
                @endif
                @if ($heading)
                    <h2 class="mt-2 text-3xl font-bold tracking-tight sm:text-4xl">{{ __($heading) }}</h2>
                @endif
                @if ($lead)
                    <p class="mt-4 text-base text-muted-foreground sm:text-lg">{{ __($lead) }}</p>
                @endif
            </div>
        @endif

        @if ($searchable)
            <div data-slot="changelog-search" class="mb-6">
                <x-ui.input type="search" autocomplete="off" x-model.debounce.120ms="query" :placeholder="__($searchPlaceholder)" :aria-label="__('Filter changes')" />
                <p class="mt-2 min-h-6 text-sm text-muted-foreground" role="status" aria-live="polite" x-text="matches === null ? '' : (matches === 0 ? {{ Js::from(__('No changes match.')) }} : {{ Js::from(__(':count matching')) }}.replace(':count', matches))"></p>
            </div>
        @endif

        <ol class="relative" aria-label="{{ __('Release history') }}">
            @foreach ($entries as $index => $entry)
                @php
                    $sections = $entry['sections'] ?? null;
                    $anchor = $entry['id'] ?? \Illuminate\Support\Str::slug((string) $entry['version']);
                @endphp
                @if ($index > 0)
                    <li aria-hidden="true" data-slot="changelog-divider" class="my-10"><x-ui.separator /></li>
                @endif
                {{-- In sections mode the release is a real heading (h2 → the
                     sections' h3), carries the anchor, and its rail stays in view
                     while a long release scrolls; the single-category shape keeps
                     its version badge. --}}
                <li @if ($sections === null) id="{{ $anchor }}" @endif data-slot="changelog-release" class="flex scroll-mt-24 flex-col gap-6 lg:flex-row lg:gap-10">
                    <div class="flex shrink-0 flex-row flex-wrap items-center gap-4 lg:sticky lg:top-24 lg:w-40 lg:flex-col lg:items-start lg:gap-2 lg:self-start lg:pt-2">
                        <span aria-hidden="true" class="mt-2 hidden size-2 shrink-0 rounded-full bg-primary ring-4 ring-background lg:block"></span>
                        @if ($sections !== null)
                            <h2 id="{{ $anchor }}" class="scroll-mt-24 text-base font-semibold leading-snug"><a href="#{{ $anchor }}" class="hover:underline">{{ $entry['version'] }}</a></h2>
                        @else
                            <x-ui.badge as="a" href="#{{ $anchor }}" variant="outline" size="sm" class="shrink-0">{{ $entry['version'] }}</x-ui.badge>
                        @endif
                        @if (! empty($entry['date']))
                            <time datetime="{{ $entry['date'] }}" class="whitespace-nowrap text-sm text-muted-foreground">{{ __($entry['dateLabel'] ?? $entry['date']) }}</time>
                        @elseif (! empty($entry['dateLabel']))
                            <span class="min-w-0 text-sm text-muted-foreground">{{ __($entry['dateLabel']) }}</span>
                        @endif
                    </div>

                    <div class="min-w-0 flex-1 space-y-4 border-s-2 border-border ps-6 lg:border-s-0 lg:ps-0">
                        @if (! empty($entry['category']) || ! empty($entry['title']))
                            <div class="flex flex-wrap items-center gap-2">
                                @if (! empty($entry['category']))
                                    <x-ui.badge :variant="$entry['variant'] ?? 'secondary'" size="sm">{{ __($entry['category']) }}</x-ui.badge>
                                @endif
                                @if (! empty($entry['title']))
                                    <h3 class="text-base font-semibold leading-snug">{{ __($entry['title']) }}</h3>
                                @endif
                            </div>
                        @endif
                        @if (! empty($entry['body']))
                            <p class="text-sm leading-relaxed text-muted-foreground">{{ __($entry['body']) }}</p>
                        @endif

                        @if ($sections !== null)
                            <div class="space-y-8">
                                @foreach ($sections as $section)
                                    <section data-slot="changelog-section">
                                        <div class="flex items-center gap-2 text-sm">
                                            <h3 class="font-semibold"><x-ui.badge :variant="$section['variant'] ?? 'secondary'" size="sm">{{ __($section['title']) }}</x-ui.badge></h3>
                                            <span data-slot="changelog-count" class="text-muted-foreground">{{ count($section['changes'] ?? []) }}</span>
                                        </div>
                                        @if (! empty($section['intro']))
                                            <div class="prose prose-sm mt-4 max-w-none text-foreground">{{ $renderChange($section['intro']) }}</div>
                                        @endif
                                        @if (! empty($section['changes']))
                                            <ul role="list" class="mt-4 space-y-2">
                                                @foreach ($section['changes'] as $change)
                                                    <li data-slot="changelog-change" class="flex items-start gap-2 text-sm text-muted-foreground">
                                                        <span aria-hidden="true" class="mt-2 size-2 shrink-0 rounded-full bg-primary"></span>
                                                        <span class="min-w-0 [&_a]:text-foreground [&_a]:underline-offset-4 [&_a:hover]:underline [&_code]:rounded-sm [&_code]:bg-muted [&_code]:px-0.5 [&_code]:font-mono [&_code]:text-foreground [&_strong]:font-semibold [&_strong]:text-foreground">{{ $renderChange($change) }}</span>
                                                    </li>
                                                @endforeach
                                            </ul>
                                        @endif
                                    </section>
                                @endforeach
                            </div>
                        @elseif (! empty($entry['changes']))
                            <ul role="list" class="space-y-2">
                                @foreach ($entry['changes'] as $change)
                                    <li data-slot="changelog-change" class="flex items-start gap-2 text-sm text-muted-foreground">
                                        <span aria-hidden="true" class="mt-2 size-2 shrink-0 rounded-full bg-primary"></span>
                                        <span class="min-w-0">{{ $renderChange($change) }}</span>
                                    </li>
                                @endforeach
                            </ul>
                        @endif
                    </div>
                </li>
            @endforeach
        </ol>
    </div>
</section>

Ownership & lifecycle

Owner, release state, review evidence and adoption for this item.
Owner
Platform UI (@JoshJML)
Current version
1.1.2
Status
Stable
License
open
Deprecation
Not deprecated
Contract
v1
Foundation
≥ 1.0.0