Компонент · рівень 1

Радіо

Вибір одного з кількох, і вибір іде в замовлення. Саме це відрізняє радіо від чипа: чип фільтрує вигляд, радіо обирає значення. Натиснув чип - змінилось, що ти бачиш; натиснув радіо - змінилось, що ти купуєш.

рівень 1design/system/components/radio.css229 рядків12 імен40 контролів · 5 екранів
Анатомія
Поле адреси, яке відкриває обрана строка

Третій блок тут - .co-sub: те, що строка відкриває під собою, коли її обрали. Рядок .co-opt:not(.on) + .co-sub ховає його, поки строка над ним не обрана - тому в демо його не видно. Натисніть другу строку, і він з'явиться.

1Кружечок .co-radio - 18×18, лише індикатор. Крапка всередині - ::after, а не другий елемент, бо розмітку пише спільний скрипт і ми її не змінюємо
2Заголовок .otn - що обираєш
3Пояснення .otd і ціна .op - строк і гроші, тобто те, чим насправді обирають
4Уся строка - зона дотику. Виміряно на чекауті: 599×71, а не 18px кружечка. Попередня редакція цієї сторінки казала «44px, а не 16px» - обидва числа були мимо
Три сім'ї, три файли, один контрол

Браузер обійшов 40 кольорових екранів на 1280 і 390: 83 елементи. Одну роботу писали три файли, і кожен розходився з двома іншими майже в усьому.

Вісь.co-opt · 21.vopt · 17.pf-lang · 2
файл до 7.29radio.cssbuy-box.csscheckout-form.css
радіус12812
падінг12/168/1216/16
край у спокої--line-hair--line-strong--line-strong
наведення--line-strong--line-inverseнемає
обраний край--line-action--line-action--line-inverse
обраний ґрунт--bg-action-soft--bg-page--bg-sunken
кружечок18×18немає20×20
недоступнонемає.offнемає
справжній inputнінітак

Дві сходинки справжні, решта була дрейфом. Радіо, що стоїть саме в стовпчику, із заголовком і причиною під ним - це строка: вона несе те, чим обирають, тому їй потрібен кружечок, і тихий край, бо сторінка в неї своя. Радіо, що стоїть плечем до плеча в наборі з чотирьох, - це компакт: на кружечок місця немає, стан несе сам край, тому він від початку на крок темніший.

Компактна сходинка
450 г900 г2270 г4540 г

Остання - .vopt.off: смак, якого нема в наявності. Перекреслений, пунктирний край, приглушене чорнило, курсор не міняється. Це єдиний стан «недоступно» в усій родині, і в атома чипа такого досі немає - пункт Кроку 6, і копіювати треба звідси.

На щаблі ще двоє, і сторінка їх не показувала. .opt-tile прийшов на 7.96 - рід тренера на coach-verify, єдиний член родини, який називає себе і пояснює себе другим рядком. .q-pill прийшов на 12.11 разом із квізом: піл, що обирає формат або бюджет, обирає ЗНАЧЕННЯ, а не вигляд, тож він радіо, а не чип. Обидва беруть радіус щабля, а не власний.

Персональний тренерПрацюю сам, 5–15 клієнтівЗал або студіяКоманда тренерів, 30+ клієнтів
ПорошокКапсулиГотові напої (RTD)
Три видимі зміни, усі на одному контролі
ЩоБулоСтало
.pf-lang у спокої--line-strong, падінг 16/16, без фону--line-hair, 12/16, --bg-page
.pf-lang.onчорнильний край + --bg-sunkenакцентний край + --bg-action-soft
.pf-lang.on .rc20×20, чорнильна заливка з білим кільцем18×18, акцентний край + акцентна крапка

Дві сім'ї з трьох казали, що обране - акцентне, і принцип каже те саме: акцент це єдиний колір дії. Отже третя приходить до них, а не навпаки. Два контроли, обидва в діалозі клієнта тренера.

Клавіатура - головне, чого не було

Виміряно на 40 екранах: жоден із 38 контролів не був досяжний з клавіатури. .co-opt це <label>, який не вказує на жоден input; .vopt це <span>. Ні role, ні tabindex, нічого не оголошується. Чекаут неможливо було пройти без миші.

Продукт уже мав відповідь у двох місцях і не вживав її в жодному: вибір мови тримає справжній input[type=radio] всередині лейбла, а зірковий вибір оцінки будується з role="radiogroup" у wireframes/_nav.js. Узято друге - перше потребує нової розмітки, а розмітка належить замороженому шару.

КлавішаЩо робитьПеревірено
Tabзаходить у групу і виходить з неї цілком - один стоп на набір, а не по одному на варіантTab з групи веде на кнопку міста
↓ ↑ → ←рухають фокус і разом з ним вибір, по колу0→1→2→0, aria-checked іде слідом
Пробіл, Enterобирають те, на чому стоїшна .off не роблять нічого
.offпропускається стрілками, aria-disabled, tabindex="-1"обхід 0→1→2→0→1, індекс 3 не відвіданий

Вибір іде через .click(), а не через клас - тому все, що сторінка вже робить на клік, працює й з клавіатури. Доказ у браузері: стрілка вниз на чекауті міняє доставку і перераховує суму - 3 999 → 4 019 → 3 939 ₴, точно як миша.

Кільце фокуса - --ring-focus-control, який існує з кроку 6.2 і якого жодне радіо не читало, бо читати його не було кому. :focus-visible, а не :focus, щоб миша його не запалювала.

ПОВЕДІНКА НА ШИРИНІ - етап 10, крок 4

Від якого порогу: жодного. Що саме міняється: група переносить варіанти через flex-wrap. Плинне з самого початку.
Коли вживати

Один із кількох, і вибір іде в замовлення: спосіб доставки, спосіб оплати, смак, розмір, мова листів. Якщо варіантів більше семи або вони не впливають на замовлення - це Меню. Якщо вибір фільтрує вигляд - це Чип.

Правило і антиправило

Правило

Уся строка натискається. 599×71 проти 18×18 - різниця між «влучив» і «промазав» на телефоні. І пояснення обов'язкове там, де варіанти різняться ціною або строком, тобто майже завжди.

Антиправило

Не роби радіо з того, що не йде в замовлення. Вкладки товару, фільтри каталогу, сортування - там теж «обери одне», але вибір міняє вигляд, а не покупку. Це Чип і Меню, у них свої атоми.
Контраст
ПараКонтрастВирок
заголовок --text-body на --bg-page15.9:1проходить
пояснення --text-secondary на --bg-page6.8:1проходить AA на 12px
обраний край --line-action3.13:1межа, не текст - для нетекстового контрасту поріг 3:1, проходить
обраний ґрунт --bg-action-soft-підкладка під тим самим чорнилом: заголовок лишається 15.4:1

Обране ніколи не тримається на одному кольорі: край, ґрунт і крапка в кружечку кажуть це втричі. Тому вибір видно і в чорно-білому друку, і людині, яка не розрізняє помаранчевий.

Натиск крок 7.33

Кожен стан цього сімейства рухав рамку, а рамка - це відповідь курсору: 1px, що змінює відтінок, читається під вістрям миші й невидиме під великим пальцем, який її ж і накрив. 38 екземплярів на телефоні - спосіб доставки, спосіб оплати, смак - і жоден не підтверджував натиск.

ЩоПід пальцемЧому
.vopt .co-opt .pf-lang, не обрані#FFF9F5--bg-action-soft, як у всього набору
обрані .onнічого.co-opt.on і .pf-lang.on у спокої вже лежать на --bg-action-soft: натиску нема куди їх вести. Тиснути на вже обране - дія без наслідку, і вдавати інше не варто
.vopt.offнічогорозпродано, cursor: default - з тієї ж причини, що й інертні клітинки перемикача вигляду
Зміряно дотиком: .vopt прозорий -> #FFF9F5, .co-opt #FFFFFF -> #FFF9F5
Холостий контроль

Сторінка звіряє себе з файлом: 12 власних класів і 4 стани оголошено в radio.css.

Рух

Робота: ВІДПОВІДЬ. Рухається: border-color, background, box-shadow, color. Тривалість --dur-fast (150ms), крива --ease-standard. Значення читаються через var() з tokens.css: підміна токена в браузері веде за собою кожне з них, тобто числа у файлі немає. При prefers-reduced-motion рух зникає, а стан лишається.

Токени
Семантичні ролі (12)Примітиви (14)
--bg-action --bg-action-soft --bg-page --line-action --line-hair --line-inverse --line-strong --ring-focus-control --text-body --text-muted --text-primary --text-secondary--font-mono --fs-12 --fs-14 --fw-bold --fw-medium --fw-semibold --radius-8 --radius-12 --radius-circle --space-2 --space-4 --space-8 --space-12 --space-16
–--dur-fast --ease-standard

Одне число без токена - 18px кружечка. Воно назване рішенням у самому файлі: 18 у 21 місці проти 20 у двох.

Розмітка
<label class="co-opt on"><span class="co-radio"></span> <span class="ot"><span class="otn">Нова Пошта</span><span class="otd">1-2 дні</span></span> <span class="op">від 50 ₴</span></label> <div class="vopts"><span class="vopt on">450 г</span><span class="vopt off">4540 г</span></div>

role, tabindex і aria-checked дописує design/_nav.js на кожній сторінці - у розмітці їх немає і не буде, доки її власником лишається заморожений сірий шар. Рядок .co-opt:not(.on) + .co-sub ховає те, що строка відкрила б, якби її обрали.

Де вживається

Якорі: .co-opt .vopt .pf-lang

5 екранів: checkout.html, checkout-loggedin.html, checkout-noaddr.html (по 2 групи, 7 контролів), product.html, product-coach.html (по 3 групи, 12 контролів), product-oos.html (1 група), account-profile.html (вибір мови в діалозі).