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

Іконка

Обгортка навколо inline-SVG. Не гліф, а коробка, у якій гліф стоїть: скільки місця займає, як тримає рядок, звідки бере колір і розмір.

Які гліфи існують, як вони намальовані, чому штрих саме такий і чому брендові марки навмисно не з набору - це Основи → Набір іконок. Тут - icon.css, тобто оправа. До кроку 7.16 обидві сторінки називались «Іконки», і чотири правила стояли на них двічі; тепер кожне має одного власника і посилання на другого.

рівень 1design/system/components/icon.css89 рядків39 екранів5 291 входження

Крок 7.16: сторінка применшувала себе вдесятеро

Тут стояло «34 рядки, 4 екрани». Виміряно на живих сторінках: .uiv-ic є на 39 екранах із 39 - на кожному - і це 5 291 входження, найчастіший клас усієї системи. Плюс .uiv-brand 62 на 31 екрані і .uiv-ic.uiv-trail 1 562. «4 екрани» рахувались по wireframes/ і по двох якорях із чотирьох.

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

Анатомія

Це демо до 7.91 малювало порожнечу, і причина в самому атомі

Тут стояли три текстові гліфи - ★, ●, ▾ - усередині .uiv-ic. Заміряно: ширина в них була (22, 20, 10), а висота нуль, тож демо займало 50px відступів і не показувало нічого. Так і має бути: .uiv-ic це display: flex з line-height: 0, і свій розмір коробка бере від SVG усередині. Текстовий знак їй розміру не дає - а перший рядок цієї ж сторінки каже «обгортка навколо inline-SVG». Демо суперечило ліду. Тепер тут data-glyph, які заповнює icons.js - той самий шлях, яким гліф приходить на екран крамниці. Знайшов це не погляд, а прохід по всіх стендах кіта на 390 і 1280, заведений на 7.91.

.chev - шеврон, який обертається, коли рядок відкривається. Це єдиний клас файлу, що має власну анімацію, і живе він у коробці, а не в гліфі: marks.js міняє знак усередині, а поворот лишається на коробці - інакше заміна забрала б його з собою. 16 входжень.

закрито ▾відкрито ▾
1Обгортка .uiv-ic - inline-flex і line-height:0 - інакше гліф зсуває рядок
2SVG - розмір у em, тому іконка росте разом з текстом
3Колір - currentColor - іконка не вирішує сама
Варіанти і розміри

Матриця осей, а не перелік входжень: кожен рядок це питання, на яке відповідає розмітка, і правило, за яким обирають.

ВісьЗначенняПравило вибору
Розмір1.05em базовий · фіксований у px там, де поруч немає текстуВ рядку - em, щоб іконка масштабувалась із підписом. Окремо - px, бо успадковувати нема від чого.
КоліруспадкованийІконка ніколи не оголошує колір сама. Хочеш іншу - зміни роль тексту, у якому вона стоїть.
Мітка в кінці.uiv-trail - 1.15em плюс --space-4 зліваСтрілка після підпису це пунктуація речення, а не іменник: вона росте з підписом, а не зі шкалою кнопки. 1 562 входження.
Брендова марка.uiv-brand - коробка --brand-ink, малюнок переростає їїКожна марка заповнює свій viewBox по-різному, тож рівна коробка дає нерівне чорнило. 62 входження.

Товщина штриха тут не оголошується. Вона задана в самих шляхах гліфів, тобто належить наборові - див. Основи → Набір іконок, розділ «Розмір, товщина, колір».

Мітка в кінці підпису

Пунктуація, а не іменник. Мітка перед підписом називає те, на що контрол діє, тож бере шкалу кнопки (18 / 20 / 24). Мітка після каже, куди веде натиск - вона належить реченню й росте з підписом: 1.15em, на волосину вище за літери поруч.

Чому це окремий клас, а не :last-child

Це єдине місце, де правило кроку 6.9 «розмітка вже каже, з якого боку» не тримає. :last-child рахує елементні діти, а підпис це голий текстовий вузол - тож у <a><span class="uiv-ic">…</span>Квіз</a> ведуча мітка теж остання елементна дитина, і правило спрацьовує на ній. Виміряно: 34 посилання навігації втратили свою мітку з 18 до 16.1 з першої спроби. CSS текстового вузла не бачить, тож бік називає той, хто мітку ставить - uivTrailMark() і menu.js.

І свій пробіл вона приносить сама. Знак ставиться з прибраним пробілом перед ним, бо кнопка це flex-рядок, чий gap уже відсуває мітку, а лишений пробіл відносив її вдвічі далі за решту набору. Посилання flex-рядком не є й не дає нічого, тож мітка ставала впритул до останньої літери - виміряно на голові мега-меню. Пробіл належить мітці там, де його не дає ніхто інший.

Цей файл вантажиться після button.css, тож за рівної специфічності саме цей рядок б'є шкалу кнопки. Порядок тут - правило, а не випадковість.

Брендова марка: коробка стала, чорнило росте
Google Apple Telegram Viber

Око було праве, а числа виглядали невинно: Apple стояв 18px проти Google 19px - п'ять відсотків різниці в коробці - і читався як дві третини розміру. Причина в тому, що кожна марка заповнює свій viewBox по-своєму, тож рівна коробка дає нерівне чорнило:

МаркаЗаповнює viewBoxЩо це
telegram · viber100%значок: диск і є марка
google83%гліф із повітрям навколо
apple72%силует, повітря ще більше

Тож компонент оголошує чорнило, якого хоче, а кожна марка виводить свою коробку з частки, яку заповнює. Множники 24/20 і 24/17.3 - виміряні на шляхах, а не вгадані.

Крок 7.3: коробка фіксована, переростає тільки малюнок

Оскільки кожна марка роздуває свій svg заради рівного чорнила, той svg став ще й найвищим усередині кнопки - тобто задавав її висоту. Виміряно на auth.html: «Продовжити з Google» 55.8 і «Продовжити з Apple» 59.6, два однакові контроли один під одним, 3.8px нарізно, обидва вище за сходинку M у 52. Марка може вирішувати, скільки їй чорнила; вона не може вирішувати, якої висоти контрол. Тож обгортка прибита до --brand-ink, а більший малюнок з неї виливається.

Крок 7.16: і все це вище було неробочим від самого початку

Виміряно на auth.html: --brand-ink: 20px, і всі чотири марки намальовані рівно на 20 - тобто саме та рівна коробка, задля уникнення якої правило й писалось. Google просив calc(20px × 1.2) = 24 і отримував 20.

Причина в одному слові, якого не було. .uiv-brand це inline-flex, прибитий до --brand-ink, отже svg усередині це flex-елемент, а flex-елемент за замовчуванням стискається. 24 не влазить у рядок 20 - і стискається до 20. overflow:visible давав малюнкові дозвіл бути намальованим поза коробкою; він ніколи не давав дозволу бути більшим. Це робить flex:none.

Після правки: Google 24, Apple 27.8 при коробці 20, а висота кнопки - те єдине, що крок 7.3 боронив, - лишилась 52. На чекауті 21.6 і 25 при коробці 18, висота 40. Контрфактично: повернув flex-shrink назад - усі знову по 20, тобто рухається саме те, про що правило.

І другий дефект, який ця правка витягла на світло. Месенджери в підвалі носять обидва класи, uiv-ic uiv-brand, і footer.css задавав їм svg{ width:16px } - тобто ліз повз компонент до малюнка. Коробка при цьому лишалась 1.05em = 14.7, і розбіжність ховав рівно той самий flex-shrink. Контракт написаний у button.css: той, хто кличе, каже, скільки чорнила; суму робить icon.css. Тепер підвал каже --brand-ink: 16px, коробка й малюнок обидва 16, рядок не зрушив.

Коли вживати

Іконка стоїть поруч зі словом, а не замість нього, всюди крім місць, де підпис уже є в aria-label і місця фізично немає (кнопка «у кошик» на картці). Принцип 5 - «звичайні слова в підписах»; піктограма без слова це загадка.

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

Правило

Іконка успадковує колір. Один клас на всі 40 гліфів, колір приходить від ролі тексту.

Антиправило

Не фарбуй іконку окремо. .uiv-ic{color:...} у файлі компонента розриває зв'язок з підписом: підпис змінить роль, а іконка лишиться старою. Так само не роби іконку-без-підпису там, де підпис влазить - див. Кнопка.
Стани, що є в коді

У коді немає жодного стану, крім базового.

Холостий контроль

Сторінка звіряє себе з файлом: 4 власних класів і 0 станів оголошено в icon.css. Нижче - ті, яких на цій сторінці немає ні в демо, ні в розмітці. Чужого контексту тут більше немає: .uiv-sort, який цей рядок називав чужим, крок 7.16 видалив - клас не зустрічається в жодному html і в жодному скрипті з кроку 7.2.

Рух

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

Стан, який його повертає, живе НЕ тут: .ord.open в order-row.css і .trustsec[open] в trust-strip.css. Атом несе свій перехід, батько перемикає стан - тому перепис станів, який читає файли поодинці, спершу назвав цю іконку рухом без стану.

Розгорнути ⌄Згорнуто ⌄
Токени

Прочитані з icon.css, не переписані руками. Колір іде лише через роль, геометрія - напряму з примітива.

Семантичні ролі (0)Примітиви (2)
---lh-flat --space-4
–--dur-fast --ease-standard

Жодної семантичної ролі, і це не пропуск. Обгортка кольору не оголошує взагалі - вона бере currentColor, тобто роль тексту, у якому стоїть. Компонент без жодної колірної ролі темою перемикається сам собою, бо перемикається текст навколо нього.

--brand-ink у списку немає навмисно: це не токен, а власна змінна компонента, оголошена тут же в .uiv-brand. Її значення читають чотири правила марок, і жодне з них не читає її ззовні.

Розмітка
<span class="uiv-ic"><svg viewBox="0 0 24 24">...</svg></span> <span class="uiv-ic uiv-trail">…</span> мітка в кінці підпису <span class="uiv-brand" data-brand="google"></span>

Розмітку майже ніде не пишуть руками: system/marks.js ставить обгортку сам, коли міняє знак на гліф, а data-brand заповнює uivBrandSvg(). Саме тому «де вживається» рахується в браузері, а не пошуком по файлах - у файлах цих 5 291 обгорток немає.

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

Якорі: .uiv-ic .uiv-trail .uiv-brand .chev

Виміряно на живих сторінках design/, а не порахувано в розмітці - більшість цих обгорток ставить marks.js уже в браузері:

КласЕкранівВходжень
.uiv-ic39 із 395 291
.uiv-ic.uiv-trail-1 562
.uiv-brand3162
.chev-16
Немає в коді -> етап 09

Чого бракує

  • Немає розмірної шкали - 1.05em це одне значення, а не сходинки. Для іконки без підпису успадковувати нема від чого, і там сьогодні фіксовані px нарізно в кожному компоненті
  • Стану немає жодного - ані ховера, ані вимкненого. Це не пропуск: обгортка кольору не має, тож усі її стани це стани тексту поруч. Але щойно з'явиться перша іконка-контрол без підпису, стан доведеться вирішувати, а не домальовувати
  • Знак поза контролом набір не малює - marks.js ходить по контролах, тож ⚠ у банері помилки OTP лишається шрифтовим на стенді, хоча в крамниці його міняє design/_nav.js. Третій випадок того самого шва після серця й кошика

Не домальовано наперед і не замовчано: етап 09 стартує з цього списку, зведеного в design/kit/docs/architecture.md.