Size Chart
Size guides — a unit-toggle measurements table with a how-to-measure panel.
Preview
@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
php artisan ui:add blocks/size-chart-table
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
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.
# 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
Usage
Render the block as a component, passing data where useful:
<x-blocks.size-chart-table />
Built from primitives
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.
@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