From e4de0673503be086afa57c01a46f4a13b4d941e6 Mon Sep 17 00:00:00 2001 From: Andreas Reinhold Date: Mon, 14 Sep 2026 05:56:52 +0200 Subject: [PATCH] Give a connected segment M3's 48px target and width MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An segment is a plain label, 32px or 40px tall, with nothing reaching past it, and nothing gave a connected its 48dp minimum width either — both numbers M3 names and tells you not to reduce. The segment now carries the shared `touch-target` utility and a 48px floor, and groups.css puts the floor on the buttons a caller nests in a connected group. drops its hand-rolled ::after for the same utility. Plan step 11, actions.md ACT-05. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9 --- resources/css/components/groups.css | 12 ++++++++++++ resources/views/components/button.blade.php | 4 ++-- resources/views/components/group.blade.php | 10 ++++++++-- tests/Feature/Components/ButtonGroupTest.php | 10 ++++++++++ tests/Feature/Components/ButtonTest.php | 4 ++-- 5 files changed, 34 insertions(+), 6 deletions(-) diff --git a/resources/css/components/groups.css b/resources/css/components/groups.css index ce05d2be..b556acd3 100644 --- a/resources/css/components/groups.css +++ b/resources/css/components/groups.css @@ -47,6 +47,18 @@ padding-inline-start: calc(var(--group-pad) - var(--group-grow)); } +/* + * A connected segment at the two smallest sizes is 32px or 40px tall, and an icon-only one is as + * narrow as it is tall. M3 names both numbers and tells you not to reduce them: "XS and S + * connected button groups have a 48dp target area and a 48dp minimum width". The target is the + * `touch-target` utility on the segment itself; the width is here, where a group can reach the + * ``s a caller put inside it. + */ +[data-button-group='connected'][data-size='xs'] > *, +[data-button-group='connected'][data-size='sm'] > * { + min-inline-size: 3rem; +} + /* * Connected segments and split halves take their inner corner from `--group-corner`, so pressing * or selecting changes one variable and the rounded outer corners stay put. diff --git a/resources/views/components/button.blade.php b/resources/views/components/button.blade.php index 0a35e9c0..c010c534 100644 --- a/resources/views/components/button.blade.php +++ b/resources/views/components/button.blade.php @@ -172,8 +172,8 @@ $outline => $variant === 'outlined', 'hover:shadow-elevation-1' => in_array($variant, ['filled', 'tonal'], true), 'shadow-elevation-1 hover:shadow-elevation-2' => $variant === 'elevated', - // Below 48px the touch target reaches past the button, as M3 requires. - 'after:absolute after:top-1/2 after:left-1/2 after:size-full after:min-h-12 after:min-w-12 after:-translate-x-1/2 after:-translate-y-1/2' => in_array($size, ['xs', 'sm'], true), + // Below 48px the touch target reaches past the button, as M3 requires (tokens/state.css). + 'touch-target' => in_array($size, ['xs', 'sm'], true), 'disabled:cursor-not-allowed disabled:shadow-none aria-disabled:pointer-events-none aria-disabled:shadow-none', 'disabled:bg-on-surface/10 disabled:text-on-surface/38 aria-disabled:bg-on-surface/10 aria-disabled:text-on-surface/38' => $contained, 'disabled:text-on-surface/38 aria-disabled:text-on-surface/38' => ! $contained, diff --git a/resources/views/components/group.blade.php b/resources/views/components/group.blade.php index d5a19826..1cff7b5a 100644 --- a/resources/views/components/group.blade.php +++ b/resources/views/components/group.blade.php @@ -10,7 +10,9 @@ bind as on any input, the arrow keys move the choice, and a screen reader announces a group. The chosen segment rounds fully and takes the selected colour; `variant` is `tonal` (the default), `filled` or `outlined`, as for toggle buttons. The segments share the row unless - `inline`. An option with `'disabled' => true` greys its own segment. + `inline`. An option with `'disabled' => true` greys its own segment. At `xs` and `sm` a + segment carries `touch-target` and a 48px minimum width, which M3 asks for by name and tells + you never to reduce. ReStride's props, kept: `label`, `hint`, `hint-class`, `name` (needed with `x-model`, which names no property), `options`, `option-value`, `option-label`; plus `option-icon`, `size`, @@ -43,6 +45,9 @@ $errorKey = $model ?? (filled($attributes->get('name')) ? str_replace(['[]', '[', ']'], ['', '.', ''], (string) $attributes->get('name')) : null); $messages = $errorKey !== null && isset($errors) ? \Illuminate\Support\Arr::flatten($errors->get($errorKey)) : []; $size = in_array($size, ['xs', 'sm', 'md', 'lg', 'xl'], true) ? $size : 'sm'; + // M3: an xs or sm connected segment keeps a 48px target and a 48px minimum width, whatever + // its 32px/40px container measures. From md the segment is wider than that on its own. + $small = in_array($size, ['xs', 'sm'], true); $segment = [ 'xs' => 'h-8 gap-2 px-3 type-label-lg', @@ -75,7 +80,8 @@