Skip to content
Brok UI

Loading…

No results

Size Chart

Open source

Size guides — a unit-toggle measurements table with a how-to-measure panel.

Preview

resources/views/blocks/size-chart-table.blade.php Blade
@props([
    'heading' => 'Size guide',
    'subheading' => 'All measurements are taken flat across the garment. If you fall between two sizes, we recommend sizing up for a relaxed fit.',
    'rows' => null,
    'measureSteps' => null,
    'fits' => null,
    'fitNote' => 'Model is 1.85 m / 6\'1" and wears a size M. Fabric has a 2-way stretch.',
])

@php
    // Each size row carries BOTH unit systems so the toggle never refetches.
    // *_cm = centimetres, *_in = inches; the active unit is chosen at render time via x-text.
    $rows ??= [
        ['size' => 'XS',  'chest_cm' => '84',  'chest_in' => '33', 'waist_cm' => '68',  'waist_in' => '27', 'hip_cm' => '90',  'hip_in' => '35', 'sleeve_cm' => '60', 'sleeve_in' => '23.5'],
        ['size' => 'S',   'chest_cm' => '90',  'chest_in' => '35', 'waist_cm' => '74',  'waist_in' => '29', 'hip_cm' => '96',  'hip_in' => '38', 'sleeve_cm' => '61', 'sleeve_in' => '24'],
        ['size' => 'M',   'chest_cm' => '96',  'chest_in' => '38', 'waist_cm' => '80',  'waist_in' => '31', 'hip_cm' => '102', 'hip_in' => '40', 'sleeve_cm' => '62', 'sleeve_in' => '24.5'],
        ['size' => 'L',   'chest_cm' => '102', 'chest_in' => '40', 'waist_cm' => '86',  'waist_in' => '34', 'hip_cm' => '108', 'hip_in' => '42', 'sleeve_cm' => '63', 'sleeve_in' => '25'],
        ['size' => 'XL',  'chest_cm' => '108', 'chest_in' => '42', 'waist_cm' => '92',  'waist_in' => '36', 'hip_cm' => '114', 'hip_in' => '45', 'sleeve_cm' => '64', 'sleeve_in' => '25'],
        ['size' => 'XXL', 'chest_cm' => '114', 'chest_in' => '45', 'waist_cm' => '98',  'waist_in' => '39', 'hip_cm' => '120', 'hip_in' => '47', 'sleeve_cm' => '65', 'sleeve_in' => '25.5'],
    ];

    $measureSteps ??= [
        [
            'icon' => 'M4 9h16 M4 9l3-3 M4 9l3 3 M20 9l-3-3 M20 9l-3 3',
            'label' => 'Chest',
            'tip' => 'Measure around the fullest part of your chest, keeping the tape level under your arms.',
        ],
        [
            'icon' => 'M7 4v16 M17 4v16 M7 12h10 M5 12h2 M17 12h2',
            'label' => 'Waist',
            'tip' => 'Wrap the tape around your natural waistline, just above the belly button.',
        ],
        [
            'icon' => 'M6 3v6a6 6 0 0 0 12 0V3 M5 21h14 M9 21l1-6 M15 21l-1-6',
            'label' => 'Hip & sleeve',
            'tip' => 'Measure the widest part of the hips, then from the shoulder seam to the cuff.',
        ],
    ];

    // Fit chips describe the cut. `active` highlights this garment's cut.
    $fits ??= [
        ['label' => 'Slim', 'active' => false],
        ['label' => 'Regular', 'active' => true],
        ['label' => 'Relaxed', 'active' => false],
    ];
@endphp

<section
    x-data="{
        unit: 'cm',
        rows: @js($rows),
    }"
    data-slot="size-chart-01"
    aria-label="{{ __('Size guide') }}"
    {{ $attributes->merge(['class' => 'bg-background py-12 text-foreground sm:py-16']) }}
>
    <div class="mx-auto max-w-6xl px-4">
        <div class="flex flex-col gap-4 sm:flex-row sm:items-end sm:justify-between">
            <div class="max-w-2xl">
                <h2 class="text-balance text-3xl font-bold tracking-tight sm:text-4xl">{{ __($heading) }}</h2>
                @if ($subheading)
                    <p class="mt-4 text-pretty text-base text-muted-foreground sm:text-lg">{{ __($subheading) }}</p>
                @endif

                @if ($fits)
                    <div class="mt-4 flex flex-wrap items-center gap-2">
                        <span class="text-sm font-medium text-muted-foreground">{{ __('Fit') }}:</span>
                        @foreach ($fits as $fit)
                            <x-ui.badge :variant="($fit['active'] ?? false) ? 'default' : 'secondary'">
                                {{ __($fit['label']) }}
                            </x-ui.badge>
                        @endforeach
                    </div>
                @endif
            </div>

            {{-- Unit toggle: a small segmented control of two buttons --}}
            <div
                role="group"
                aria-label="{{ __('Measurement unit') }}"
                class="inline-flex shrink-0 items-center gap-2 rounded-md border border-border bg-muted/50 p-2"
            >
                <x-ui.button
                    type="button"
                    variant="ghost"
                    size="sm"
                    x-on:click="unit = 'cm'"
                    x-bind:aria-pressed="unit === 'cm'"
                    x-bind:class="unit === 'cm' ? 'bg-background text-foreground shadow-sm' : 'text-muted-foreground'"
                    class="rounded-md"
                >
                    {{ __('cm') }}
                </x-ui.button>
                <x-ui.button
                    type="button"
                    variant="ghost"
                    size="sm"
                    x-on:click="unit = 'in'"
                    x-bind:aria-pressed="unit === 'in'"
                    x-bind:class="unit === 'in' ? 'bg-background text-foreground shadow-sm' : 'text-muted-foreground'"
                    class="rounded-md"
                >
                    {{ __('in') }}
                </x-ui.button>
            </div>
        </div>

        <div class="mt-8 grid grid-cols-1 gap-6 lg:grid-cols-3">
            {{-- Size table --}}
            <div class="lg:col-span-2">
                <div class="overflow-x-auto rounded-lg border border-border shadow-sm">
                    <x-ui.table class="min-w-[34rem]">
                        <caption class="sr-only">
                            {{ __('Garment measurements by size, shown in the selected unit') }}
                        </caption>

                        <x-ui.table.header>
                            <x-ui.table.row class="hover:bg-transparent">
                                <x-ui.table.head scope="col">{{ __('Size') }}</x-ui.table.head>
                                <x-ui.table.head scope="col" class="text-end">{{ __('Chest') }}</x-ui.table.head>
                                <x-ui.table.head scope="col" class="text-end">{{ __('Waist') }}</x-ui.table.head>
                                <x-ui.table.head scope="col" class="text-end">{{ __('Hip') }}</x-ui.table.head>
                                <x-ui.table.head scope="col" class="text-end">{{ __('Sleeve') }}</x-ui.table.head>
                            </x-ui.table.row>
                        </x-ui.table.header>

                        <x-ui.table.body>
                            <template x-for="row in rows" x-bind:key="row.size">
                                <x-ui.table.row class="hover:bg-muted/40">
                                    <x-ui.table.head scope="row" class="font-semibold text-foreground" x-text="row.size"></x-ui.table.head>
                                    <x-ui.table.cell class="text-end tabular-nums" x-text="unit === 'cm' ? row.chest_cm : row.chest_in"></x-ui.table.cell>
                                    <x-ui.table.cell class="text-end tabular-nums" x-text="unit === 'cm' ? row.waist_cm : row.waist_in"></x-ui.table.cell>
                                    <x-ui.table.cell class="text-end tabular-nums" x-text="unit === 'cm' ? row.hip_cm : row.hip_in"></x-ui.table.cell>
                                    <x-ui.table.cell class="text-end tabular-nums" x-text="unit === 'cm' ? row.sleeve_cm : row.sleeve_in"></x-ui.table.cell>
                                </x-ui.table.row>
                            </template>
                        </x-ui.table.body>
                    </x-ui.table>
                </div>

                <p class="mt-4 text-sm text-muted-foreground">
                    {{ __('Values shown in') }}
                    <span class="font-medium text-foreground" x-text="unit === 'cm' ? '{{ __('centimetres') }}' : '{{ __('inches') }}'"></span>.
                </p>
            </div>

            {{-- How to measure --}}
            <x-ui.card class="flex flex-col gap-6 p-6">
                <h3 class="text-base font-semibold tracking-tight text-foreground">{{ __('How to measure') }}</h3>

                <ol role="list" class="flex flex-col gap-4">
                    @foreach ($measureSteps as $index => $step)
                        <li class="flex items-start gap-4">
                            <span class="flex size-10 shrink-0 items-center justify-center rounded-md bg-primary/10 text-primary">
                                <svg
                                    class="size-5"
                                    viewBox="0 0 24 24"
                                    fill="none"
                                    stroke="currentColor"
                                    stroke-width="1.75"
                                    stroke-linecap="round"
                                    stroke-linejoin="round"
                                    aria-hidden="true"
                                >
                                    <path d="{{ $step['icon'] }}" />
                                </svg>
                            </span>
                            <div class="min-w-0">
                                <p class="text-sm font-semibold text-foreground">
                                    <span class="text-muted-foreground">{{ $index + 1 }}.</span>
                                    {{ __($step['label']) }}
                                </p>
                                <p class="mt-0.5 text-pretty text-sm text-muted-foreground">{{ __($step['tip']) }}</p>
                            </div>
                        </li>
                    @endforeach
                </ol>

                @if ($fitNote)
                    <x-ui.separator />
                    <p class="text-pretty text-sm text-muted-foreground">{{ __($fitNote) }}</p>
                @endif
            </x-ui.card>
        </div>
    </div>
</section>

Installation

terminal
php artisan ui:add blocks/size-chart-table

Installs these dependencies automatically:

Registry contract

Install confidence

php artisan ui:add blocks/size-chart-table 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.0.3
License
open
Stability
stable
Contract
v1
Foundation
≥ 1.0.0
Type Generated target
blade resources/views/blocks/size-chart-table.blade.php

Registry dependencies

table card button badge separator

Package dependencies

composer: jml/brok:^0.2

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.

size-chart-table.md
# Brok UI block: Size Chart (`size-chart-table`)

An ecommerce size guide with a cm/in unit toggle: a segmented control flips every measurement between centimetres and inches in place. An accessible table lists sizes (XS-XXL) down the side with Chest, Waist, Hip and Sleeve columns, each row carrying both unit values and rendered via the active unit. A side panel walks through three illustrated 'how to measure' steps with a fit note, plus a Slim/Regular/Relaxed fit badge row.

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/size-chart-table
```

## Render it

```blade
<x-blocks.size-chart-table />
```

## Props

- `heading` (string, default `Size guide`) — Section heading.
- `subheading` (string, default `All measurements are taken flat across the garment. If you fall between two sizes, we recommend sizing up for a relaxed fit.`) — Supporting copy under the heading.
- `rows` (mixed|null, default `null`) — Size rows carrying both centimetre and inch measurements; null falls back to sample XS-XXL data.
- `measure-steps` (mixed|null, default `null`) — Illustrated how-to-measure steps; null falls back to sample data.
- `fits` (mixed|null, default `null`) — Fit chips (Slim/Regular/Relaxed) with an active flag; null falls back to sample data.
- `fit-note` (string, default `Model is 1.85 m / 6\'1" and wears a size M. Fabric has a 2-way stretch.`) — Model sizing note shown under the measure steps.

## Use when

- Publishing an ecommerce size guide with a centimetre/inch unit toggle, a measurements table and illustrated how-to-measure steps.
- Helping shoppers pick the right size on a product page.

## Avoid when

- Displaying a plain data table without sizing-specific unit toggling; use the table primitive directly instead.

## Rules

- Render the installed block with `<x-blocks.size-chart-table />` 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/size-chart-table
- Registry JSON (files, props, contract): https://brokui.dev/r/open/blocks/size-chart-table.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 an ecommerce size guide with a centimetre/inch unit toggle, a measurements table and illustrated how-to-measure steps.
  • Helping shoppers pick the right size on a product page.

Avoid when

  • Displaying a plain data table without sizing-specific unit toggling; use the table primitive directly instead.

Anti-patterns

  • Do not replace semantic props with conflicting utility classes.
  • Do not remove labels, focus styles, or state attributes.
Anatomy
root size-chart-01

Usage

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

resources/views/welcome.blade.php Blade
<x-blocks.size-chart-table />

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/size-chart-table.blade.php Blade
@props([
    'heading' => 'Size guide',
    'subheading' => 'All measurements are taken flat across the garment. If you fall between two sizes, we recommend sizing up for a relaxed fit.',
    'rows' => null,
    'measureSteps' => null,
    'fits' => null,
    'fitNote' => 'Model is 1.85 m / 6\'1" and wears a size M. Fabric has a 2-way stretch.',
])

@php
    // Each size row carries BOTH unit systems so the toggle never refetches.
    // *_cm = centimetres, *_in = inches; the active unit is chosen at render time via x-text.
    $rows ??= [
        ['size' => 'XS',  'chest_cm' => '84',  'chest_in' => '33', 'waist_cm' => '68',  'waist_in' => '27', 'hip_cm' => '90',  'hip_in' => '35', 'sleeve_cm' => '60', 'sleeve_in' => '23.5'],
        ['size' => 'S',   'chest_cm' => '90',  'chest_in' => '35', 'waist_cm' => '74',  'waist_in' => '29', 'hip_cm' => '96',  'hip_in' => '38', 'sleeve_cm' => '61', 'sleeve_in' => '24'],
        ['size' => 'M',   'chest_cm' => '96',  'chest_in' => '38', 'waist_cm' => '80',  'waist_in' => '31', 'hip_cm' => '102', 'hip_in' => '40', 'sleeve_cm' => '62', 'sleeve_in' => '24.5'],
        ['size' => 'L',   'chest_cm' => '102', 'chest_in' => '40', 'waist_cm' => '86',  'waist_in' => '34', 'hip_cm' => '108', 'hip_in' => '42', 'sleeve_cm' => '63', 'sleeve_in' => '25'],
        ['size' => 'XL',  'chest_cm' => '108', 'chest_in' => '42', 'waist_cm' => '92',  'waist_in' => '36', 'hip_cm' => '114', 'hip_in' => '45', 'sleeve_cm' => '64', 'sleeve_in' => '25'],
        ['size' => 'XXL', 'chest_cm' => '114', 'chest_in' => '45', 'waist_cm' => '98',  'waist_in' => '39', 'hip_cm' => '120', 'hip_in' => '47', 'sleeve_cm' => '65', 'sleeve_in' => '25.5'],
    ];

    $measureSteps ??= [
        [
            'icon' => 'M4 9h16 M4 9l3-3 M4 9l3 3 M20 9l-3-3 M20 9l-3 3',
            'label' => 'Chest',
            'tip' => 'Measure around the fullest part of your chest, keeping the tape level under your arms.',
        ],
        [
            'icon' => 'M7 4v16 M17 4v16 M7 12h10 M5 12h2 M17 12h2',
            'label' => 'Waist',
            'tip' => 'Wrap the tape around your natural waistline, just above the belly button.',
        ],
        [
            'icon' => 'M6 3v6a6 6 0 0 0 12 0V3 M5 21h14 M9 21l1-6 M15 21l-1-6',
            'label' => 'Hip & sleeve',
            'tip' => 'Measure the widest part of the hips, then from the shoulder seam to the cuff.',
        ],
    ];

    // Fit chips describe the cut. `active` highlights this garment's cut.
    $fits ??= [
        ['label' => 'Slim', 'active' => false],
        ['label' => 'Regular', 'active' => true],
        ['label' => 'Relaxed', 'active' => false],
    ];
@endphp

<section
    x-data="{
        unit: 'cm',
        rows: @js($rows),
    }"
    data-slot="size-chart-01"
    aria-label="{{ __('Size guide') }}"
    {{ $attributes->merge(['class' => 'bg-background py-12 text-foreground sm:py-16']) }}
>
    <div class="mx-auto max-w-6xl px-4">
        <div class="flex flex-col gap-4 sm:flex-row sm:items-end sm:justify-between">
            <div class="max-w-2xl">
                <h2 class="text-balance text-3xl font-bold tracking-tight sm:text-4xl">{{ __($heading) }}</h2>
                @if ($subheading)
                    <p class="mt-4 text-pretty text-base text-muted-foreground sm:text-lg">{{ __($subheading) }}</p>
                @endif

                @if ($fits)
                    <div class="mt-4 flex flex-wrap items-center gap-2">
                        <span class="text-sm font-medium text-muted-foreground">{{ __('Fit') }}:</span>
                        @foreach ($fits as $fit)
                            <x-ui.badge :variant="($fit['active'] ?? false) ? 'default' : 'secondary'">
                                {{ __($fit['label']) }}
                            </x-ui.badge>
                        @endforeach
                    </div>
                @endif
            </div>

            {{-- Unit toggle: a small segmented control of two buttons --}}
            <div
                role="group"
                aria-label="{{ __('Measurement unit') }}"
                class="inline-flex shrink-0 items-center gap-2 rounded-md border border-border bg-muted/50 p-2"
            >
                <x-ui.button
                    type="button"
                    variant="ghost"
                    size="sm"
                    x-on:click="unit = 'cm'"
                    x-bind:aria-pressed="unit === 'cm'"
                    x-bind:class="unit === 'cm' ? 'bg-background text-foreground shadow-sm' : 'text-muted-foreground'"
                    class="rounded-md"
                >
                    {{ __('cm') }}
                </x-ui.button>
                <x-ui.button
                    type="button"
                    variant="ghost"
                    size="sm"
                    x-on:click="unit = 'in'"
                    x-bind:aria-pressed="unit === 'in'"
                    x-bind:class="unit === 'in' ? 'bg-background text-foreground shadow-sm' : 'text-muted-foreground'"
                    class="rounded-md"
                >
                    {{ __('in') }}
                </x-ui.button>
            </div>
        </div>

        <div class="mt-8 grid grid-cols-1 gap-6 lg:grid-cols-3">
            {{-- Size table --}}
            <div class="lg:col-span-2">
                <div class="overflow-x-auto rounded-lg border border-border shadow-sm">
                    <x-ui.table class="min-w-[34rem]">
                        <caption class="sr-only">
                            {{ __('Garment measurements by size, shown in the selected unit') }}
                        </caption>

                        <x-ui.table.header>
                            <x-ui.table.row class="hover:bg-transparent">
                                <x-ui.table.head scope="col">{{ __('Size') }}</x-ui.table.head>
                                <x-ui.table.head scope="col" class="text-end">{{ __('Chest') }}</x-ui.table.head>
                                <x-ui.table.head scope="col" class="text-end">{{ __('Waist') }}</x-ui.table.head>
                                <x-ui.table.head scope="col" class="text-end">{{ __('Hip') }}</x-ui.table.head>
                                <x-ui.table.head scope="col" class="text-end">{{ __('Sleeve') }}</x-ui.table.head>
                            </x-ui.table.row>
                        </x-ui.table.header>

                        <x-ui.table.body>
                            <template x-for="row in rows" x-bind:key="row.size">
                                <x-ui.table.row class="hover:bg-muted/40">
                                    <x-ui.table.head scope="row" class="font-semibold text-foreground" x-text="row.size"></x-ui.table.head>
                                    <x-ui.table.cell class="text-end tabular-nums" x-text="unit === 'cm' ? row.chest_cm : row.chest_in"></x-ui.table.cell>
                                    <x-ui.table.cell class="text-end tabular-nums" x-text="unit === 'cm' ? row.waist_cm : row.waist_in"></x-ui.table.cell>
                                    <x-ui.table.cell class="text-end tabular-nums" x-text="unit === 'cm' ? row.hip_cm : row.hip_in"></x-ui.table.cell>
                                    <x-ui.table.cell class="text-end tabular-nums" x-text="unit === 'cm' ? row.sleeve_cm : row.sleeve_in"></x-ui.table.cell>
                                </x-ui.table.row>
                            </template>
                        </x-ui.table.body>
                    </x-ui.table>
                </div>

                <p class="mt-4 text-sm text-muted-foreground">
                    {{ __('Values shown in') }}
                    <span class="font-medium text-foreground" x-text="unit === 'cm' ? '{{ __('centimetres') }}' : '{{ __('inches') }}'"></span>.
                </p>
            </div>

            {{-- How to measure --}}
            <x-ui.card class="flex flex-col gap-6 p-6">
                <h3 class="text-base font-semibold tracking-tight text-foreground">{{ __('How to measure') }}</h3>

                <ol role="list" class="flex flex-col gap-4">
                    @foreach ($measureSteps as $index => $step)
                        <li class="flex items-start gap-4">
                            <span class="flex size-10 shrink-0 items-center justify-center rounded-md bg-primary/10 text-primary">
                                <svg
                                    class="size-5"
                                    viewBox="0 0 24 24"
                                    fill="none"
                                    stroke="currentColor"
                                    stroke-width="1.75"
                                    stroke-linecap="round"
                                    stroke-linejoin="round"
                                    aria-hidden="true"
                                >
                                    <path d="{{ $step['icon'] }}" />
                                </svg>
                            </span>
                            <div class="min-w-0">
                                <p class="text-sm font-semibold text-foreground">
                                    <span class="text-muted-foreground">{{ $index + 1 }}.</span>
                                    {{ __($step['label']) }}
                                </p>
                                <p class="mt-0.5 text-pretty text-sm text-muted-foreground">{{ __($step['tip']) }}</p>
                            </div>
                        </li>
                    @endforeach
                </ol>

                @if ($fitNote)
                    <x-ui.separator />
                    <p class="text-pretty text-sm text-muted-foreground">{{ __($fitNote) }}</p>
                @endif
            </x-ui.card>
        </div>
    </div>
</section>

Ownership & lifecycle

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