Текст в интерфейсе: формулировки, сообщения об ошибках, тон
Откройте любой экран своего продукта и мысленно удалите с него весь текст. Останутся прямоугольники, иконки и отступы — и ни одного способа понять, что здесь происходит. Интерфейс на 80–90% состоит из слов: заголовки, подписи полей, названия кнопок, подсказки, ошибки, пустые экраны, письма и пуши. Всё остальное — способ эти слова расположить.
Отсюда неприятный вывод для команды: текст в интерфейсе — не «оформление», которое дописывают в последний день перед релизом, а такая же часть конструкции, как навигация или состояния. Если пользователь не нашёл кнопку, чаще виновата не её позиция, а её название. Если форма «неудобная», в половине случаев дело не в расстояниях между полями, а в том, что человек не понимает, что от него хотят и зачем.
Эта статья — про то, как проектировать текст, а не «писать красиво»: где в интерфейсе текст живёт и на какой вопрос отвечает каждый кусок; почему конкретные формы бесят и как это чинится; из чего состоит нормальное сообщение об ошибке; как устроен тон и где заканчиваются данные и начинается вкусовщина; как считать «файлы» и «файла» и почему интерфейс разъезжается при переводе; как передавать строки в разработку так, чтобы они не превращались в скриншот в задаче. Предполагается, что вы уже прошли пользовательские сценарии, информационную архитектуру и состояния интерфейса: без сценария непонятно, какой вопрос у человека в голове, без IA — каким словарём называть сущности, без карты состояний — сколько вообще строк надо написать.
Первый принцип: текст отвечает на вопрос, который у человека уже есть
Никто не читает интерфейс. Люди сканируют его в поисках ответа на конкретный вопрос, который у них возник секунду назад. Классические измерения Nielsen Norman Group показывают: в среднем на веб-странице человек успевает прочитать порядка 20–28% слов, а взгляд движется не по строкам, а рваными паттернами — так называемым F-образным паттерном и его вариациями. Подробности в статье «How Little Do Users Read?».
Практический вывод не «пишите короче», а каждый кусок текста отвечает ровно на один вопрос пользователя, и этот вопрос должен быть у него именно в этот момент. Текст, отвечающий на вопрос, которого нет, не читают, каким бы коротким он ни был. А вопросы на любом экране довольно однообразны:
- Где я и что это за место?
- Что от меня хотят прямо сейчас?
- Что будет, если я нажму вот это?
- Почему у меня не получилось и что делать?
- Всё получилось? Точно?
- Как отсюда уйти, ничего не сломав?
Хороший интерфейсный текст — это отображение «вопрос → слот на экране». Плохой — набор фраз, которые дизайнер посчитал уместными.
Обратите внимание на нижнюю ветку. Текст живёт не только внутри экрана: письмо «подтвердите почту», push «платёж не прошёл» и ответ API {"error": "invalid_state"}, который однажды всплывёт в интерфейсе, — часть той же системы. Если их пишут разные люди без общего словаря, продукт разговаривает несколькими голосами одновременно.
Карта слотов: где именно живёт текст
Прежде чем спорить о формулировках, полезно договориться о слотах. Слот — это позиция в интерфейсе с фиксированной ролью: у неё есть свой вопрос, свои ограничения по длине и свои правила поведения.
| Слот | Вопрос пользователя | Типичная длина | Частая ошибка |
|---|---|---|---|
| Заголовок экрана | «Где я?» | 1–4 слова | Не совпадает с названием пункта меню, откуда пришли |
| Подзаголовок | «Что от меня требуется?» | 1 предложение | Пересказывает заголовок другими словами |
| Подпись поля | «Что сюда вводить?» | 1–3 слова | Заменена плейсхолдером и исчезает при вводе |
| Плейсхолдер | «В каком формате?» | пример значения | Несёт смысл, который пропадает при первом символе |
| Подсказка под полем | «Зачем вам это и что будет дальше?» | 1 предложение | Показывается только после ошибки |
| Кнопка | «Что произойдёт, если нажму?» | 1–3 слова, глагол | «ОК», «Продолжить», «Отправить» без объекта |
| Ошибка | «Что не так и как починить?» | 1–2 предложения | Название технического исключения |
| Пустое состояние | «Почему пусто и что делать?» | заголовок + строка + действие | Просто «Нет данных» |
| Подтверждение и успех | «Это необратимо? Всё получилось?» | вопрос + последствие | «Вы уверены?» без последствий; тост исчезает раньше, чем прочитан |
Дальше — три подробных разбора: кнопка, форма, ошибка. Это три места, где текст чаще всего решает судьбу сценария.
Разбор №1: почему пользователь не нашёл кнопку
Ситуация из реального теста B2B-панели. Задача: «выгрузите отчёт за прошлый месяц, чтобы отправить бухгалтеру». Шесть человек из восьми не справились за отведённые три минуты. Кнопка была на экране, в правом верхнем углу, ничем не закрыта. Называлась «Экспорт». Разберём по слоям — каждый слой требует своего лечения.
Слой 1. Словарь системы вместо словаря пользователя. Люди искали глазами слова «скачать», «выгрузить», «Excel». «Экспорт» — слово из мира разработчика: так называется операция в коде. Проверка простая: в интервью и в тестах записывайте дословно, какими словами человек описывает задачу до того, как увидит интерфейс. Если в записи пять раз «выгрузить в эксель» и ноль раз «экспорт» — вопрос закрыт. Это прямое продолжение работы с именованием в информационной архитектуре и данных из исследований.
Слой 2. Иконка вместо слова. В том же интерфейсе рядом стояли восемь иконок в тулбаре, и «Экспорт» была одной из них — стрелка вниз. Иконка без подписи распознаётся надёжно только у нескольких десятков символов, которые люди видели тысячи раз: дом, лупа, корзина, крестик. Всё остальное — угадайка. Тултип не спасает: он появляется через 500–700 мс наведения, а сканирование идёт быстрее. Лечение: подпись рядом с иконкой, а не вместо неё. Если места нет — значит, в тулбаре слишком много действий, и это проблема приоритизации, а не текста.
Слой 3. Кнопка описывает механику, а не результат. Даже переименовав в «Скачать», мы не закрываем вопрос «а в каком виде?». «Скачать CSV» отвечает сразу. Хорошая формула названия кнопки: глагол + объект (+ ключевой параметр, если он снимает страх).
Слой 4. Кнопка не снимает страх. На предпоследнем шаге оплаты кнопка «Продолжить» вызывает заминку: спишут деньги прямо сейчас или нет? Человек ищет глазами дополнительные подтверждения, иногда уходит проверять корзину и не возвращается. Разведите шаги словами: на предпоследнем — «Перейти к оплате», на последнем — «Оплатить 1 490 руб.». Второе название одновременно сообщает сумму, необратимость и то, что дальше шага не будет.
| Было | Стало | Что именно починили |
|---|---|---|
| Экспорт | Скачать CSV | словарь пользователя + результат вместо операции |
| ОК | Удалить проект | понятно без чтения модалки, какой из вариантов какой |
| Отправить | Отправить заявку | понятно, что именно уходит и кому |
| Продолжить | Оплатить 1 490 руб. | снята неопределённость по деньгам и необратимости |
| Сохранить / Применить рядом | Сохранить черновик / Опубликовать | два слова-синонима заменены на два разных результата |
| Готово | Закрыть без сохранения | честно названо последствие |
Отдельный случай — кнопки в диалогах. «Вы уверены? [Да] [Нет]» заставляет перечитать вопрос, чтобы понять, что означает «Да». Пара «[Удалить 12 файлов] [Отмена]» читается без вопроса вообще. Это же требование прописано в WCAG как критерий 2.5.3 Label in Name: видимая надпись должна входить в программное имя элемента, иначе голосовое управление ломается — человек говорит «нажми Удалить», а система знает кнопку как «confirm_dialog_primary».
Разбор №2: почему форма неудобна
«Неудобная форма» — самая частая и самая бесполезная формулировка в отзывах. Разбираем на конкретных механизмах.
Плейсхолдер вместо подписи — самый популярный способ убить форму дизайном. Подпись внутри поля выглядит аккуратно в макете и разрушается в реальности: как только человек начинает печатать, подпись исчезает. Дальше цепочка: невозможно проверить заполненное перед отправкой; при ошибке непонятно, какое из полей какое; серый текст на белом почти всегда не проходит по контрасту; скринридеры читают плейсхолдер непредсказуемо; автозаполнение перекрывает его совсем. Лечение: подпись всегда видна над полем, плейсхолдер — только пример формата (+7 912 345-67-89), причём такой, чтобы его нельзя было спутать с уже введённым значением. Разбор — «Placeholders in Form Fields Are Harmful» у NN/g, механика доступности — в статье «Доступные формы».
Подсказка появляется только после ошибки. Классика: требования к паролю показываются красным после того, как пользователь придумал пароль и нажал «Зарегистрироваться». Это буквально «сначала сделай неправильно, потом мы объясним правила». То же с лимитом на размер файла: «файл слишком большой» после сорока секунд загрузки на мобильном интернете. Правило: ограничение показывается до ввода, а не после — «От 8 символов, минимум одна цифра» под полем пароля; «PDF или JPG, до 5 МБ» рядом с кнопкой выбора файла. Ошибка тогда становится редким событием, а не основным каналом коммуникации.
Непонятно, зачем это спрашивают. Поле «Кодовое слово» в заявке на карту стабильно даёт всплеск отказов: человек не знает, что это, боится придумать что-то не то и уходит «спросить». Подсказка «Понадобится, если вы позвоните в банк: оператор спросит его вместо паспортных данных» снимает вопрос целиком. Чем чувствительнее данные, тем обязательнее объяснение: телефон, ИНН, дата рождения, доступ к контактам — всё это требует одной строки «зачем».
Двойное отрицание в чекбоксах. «☐ Не присылать мне уведомления об акциях» — чтобы отказаться, надо поставить галочку в отрицании; человек читает три раза. Формулируйте чекбокс как положительное утверждение о том, что произойдёт при включении: «☑ Присылать письма о статусе заказа». И разделяйте сущности: транзакционные письма и маркетинг — разные чекбоксы, потому что это разные решения.
Звёздочки и обязательность. «Обязательные поля отмечены *» в форме, где 11 полей из 12 обязательны, — шум. Инвертируйте: помечайте словом «необязательно» те немногие, что необязательны. Звёздочка сама по себе не сообщает обязательность ни скринридеру, ни человеку, который её раньше не встречал.
Маски ввода, которые дерутся с пользователем. Маска +7 (___) ___-__-__ ломается, когда номер вставляют из буфера как 8 916 1234567 или +79161234567. Правильное поведение — нормализовать ввод на своей стороне и написать под полем «можно вставить в любом формате». Текст здесь не украшение: он сообщает о снятом ограничении, которое иначе невидимо.
Ниже — жизненный цикл одного поля со всеми текстами, которые к нему прилагаются. Полезно рисовать такую диаграмму до макета: сразу видно, сколько строк нужно написать на самом деле.
Два важных момента, которые видны только на такой схеме.
Момент проверки. Валидация «на каждый символ» превращает ввод почты в поток красного текста: пока человек не дописал @example.com, всё «неверно». Baymard Institute в исследованиях форм показывает, что польза инлайн-валидации целиком зависит от момента срабатывания: проверка после потери фокуса помогает, преждевременная — раздражает и повышает отказ (разбор). Практическое правило: первый раз проверяем по blur; после того как ошибка показана, для этого поля переходим на проверку по каждому вводу, чтобы человек видел, как ошибка исчезает.
Состояние «заблокировано». Дизайнеры почти всегда рисуют серое поле и забывают текст. А у пользователя ровно один вопрос: почему. «Изменить нельзя после отправки декларации» — одна строка, которая закрывает поток обращений в поддержку.
Разбор №3: сообщения об ошибках
Ошибка — это момент, когда пользователь одновременно расстроен, торопится и максимально внимателен к тексту. Единственная точка в интерфейсе, где текст читают целиком. Поэтому цена формулировки здесь выше всего.
Классический канон — Error Message Guidelines NN/g: сообщение должно быть заметным, написанным человеческим языком, вежливым, точным и содержать конструктивный совет. Добавим к этому две вещи, которых в каноне нет, а в продакшене они критичны: судьба введённых данных и идентификатор для поддержки.
| Было | Стало | Почему |
|---|---|---|
| Произошла ошибка. Попробуйте позже | Файл 12 МБ, а можно до 5 МБ. Сожмите или выберите другой | названа причина и действие; лимит указан числом |
| Неверный формат данных | Дата в формате ДД.ММ.ГГГГ, например 05.03.1990 | правило показано на примере, а не описано термином |
| Ошибка 403 | У вас нет прав на публикацию. Попросите доступ у администратора проекта — Анны Ким | человеку названо, к кому идти |
| Не удалось сохранить | Не сохранили черновик: пропала связь. Текст остался на устройстве, нажмите «Повторить» | сообщена судьба данных, есть кнопка действия |
| Что-то пошло не так | Не смогли построить отчёт. Мы уже чиним, обычно занимает 10–15 минут. Код для поддержки: RPT-8841 | честно про статус, дан код, названо ожидание |
| Пароль не соответствует требованиям | Добавьте цифру — остальное в пароле в порядке | сказано ровно то, что осталось сделать |
Несколько правил, которые стоит зафиксировать в гайде команды. Не обвиняйте: «Вы ввели неверный номер» → «Этот номер не подходит: в нём 15 цифр вместо 16». Разница не в вежливости, а в фокусе — во втором варианте описан факт, а не оценка человека.
Не врите про причину. «Попробуйте позже» при ошибке прав доступа — ложь: позже ничего не изменится. Пользователь потратит полчаса на повторные попытки, потом придёт в поддержку злым. Разные причины — разные тексты, даже если экран один.
Не показывайте код вместо сообщения, но и не выбрасывайте его. Код нужен поддержке: по нему находят конкретный запрос в логах. Значит, это должен быть correlation id, а не номер исключения, и его надо давать копируемым — в конце сообщения, мелким шрифтом.
Сообщайте, что случилось с данными. Самый страшный вопрос при ошибке — «я потерял то, что писал сорок минут?». Одна строка «черновик сохранён» стоит больше, чем весь остальной текст. И наоборот: не показывайте ошибку там, где её можно не показывать — автоматически обрежьте пробелы в номере карты, а не ругайтесь на них. Каждая ошибка, удалённая из системы, лучше самой хорошо написанной.
Где показывать сообщение — отдельное проектное решение, зависящее от масштаба поломки и от того, может ли пользователь продолжать работу.
продолжить работу?"} B -- "Нет, весь экран не работает" --> C{"Данные пользователя
под угрозой?"} C -- "Да" --> D["Блокирующая модалка:
что случилось, что с данными,
кнопка восстановления"] C -- "Нет" --> E["Экран ошибки на месте контента:
причина плюс кнопка «Повторить»"] B -- "Частично" --> F{"Ошибка привязана
к конкретному полю?"} F -- "Да" --> G["Текст под полем плюс aria-invalid,
фокус на первое ошибочное поле"] F -- "Нет, ошибка всей формы" --> H["Баннер над формой со списком
проблем и якорями на поля"] B -- "Да, фон" --> I{"Пользователь
инициировал действие?"} I -- "Да" --> J["Тост с действием
«Повторить» или «Отменить»"] I -- "Нет, фоновая синхронизация" --> K["Тихий индикатор в статус-баре"] D --> L["Показываем correlation id
для поддержки"] E --> L G --> M["Сообщение объявляется
через live-region"] H --> M J --> M
Два узла этой схемы — про доступность, и они не случайно попали в схему выбора места. Куда переводится фокус и объявляется ли сообщение — это решение дизайнера, а не разработчика: именно вы знаете, в каком порядке человек должен узнать о проблеме. Механика — в доступности на этапе дизайна и подробно в статье «Доступные формы».
Отдельно про безопасность. «Неверный логин или пароль» — намеренно размытая формулировка: точное «такого пользователя нет» позволяет перебором собрать базу существующих аккаунтов. Это честный конфликт между юзабилити и защитой, и решается он не текстом, а конструкцией: после нескольких неудачных попыток предложите восстановление пароля прямо в сообщении. Смежная тема — в треке security.
Тон: как он устроен и где начинается вкусовщина
Тон часто обсуждают как черту бренда: «мы дружелюбные», «мы серьёзные». Это полуправда. У бренда есть голос — он постоянен; тон — это модуляция голоса под ситуацию, и он обязан меняться. NN/g предлагает раскладывать тон по четырём измерениям: смешной ↔ серьёзный, формальный ↔ разговорный, почтительный ↔ дерзкий, восторженный ↔ сдержанный. Полезность не в самой классификации, а в том, что по этим осям можно договориться с командой без слов «как-то не так звучит». Главное правило: чем выше цена ошибки и стресс пользователя, тем ближе к сухому и точному краю шкалы. Шутка на пустом экране «Здесь пока пусто» уместна; та же шутка при отказе платежа читается как издевательство.
Ещё несколько закономерностей, которые не про вкус. «Мы» и «вы». Используйте «мы», когда ответственность на системе («Мы не смогли связаться с банком»), и «вы» — когда действие принадлежит пользователю («Вы сможете отменить в течение часа»). Ошибка — писать «вы» там, где виновата система, и «мы» там, где решает пользователь. Второе особенно неприятно в согласиях: «мы обработаем ваши данные» вместо «вы разрешаете обработку» перекладывает субъектность.
Императив в кнопках, инфинитив в заголовках. «Оплатить», «Скачать» — на кнопках работает лучше всего, потому что кнопка отвечает на вопрос «что произойдёт». Заголовки лучше именные: «Оплата заказа», а не «Оплатите заказ» — заголовок называет место, а не командует.
Сокращения и термины. Термин допустим, если он у пользователя в голове раньше, чем в вашем интерфейсе. «Эквайринг» в панели для мерчантов — нормально. То же слово в приложении для покупателей — нет.
А вот честный список того, что является вкусовщиной и не должно занимать время команды дольше пятнадцати минут: «вы» с прописной или строчной; точка в конце подсказки; «Войти» против «Вход»; заглавные буквы в кнопках; тире или двоеточие в заголовке. Это вопрос стиля бренда. Правильный способ закрыть их — один раз зафиксировать в контент-гайде и больше не обсуждать; вечно спорить о них на ревью — способ не заниматься реальными проблемами.
Что доказано, а что стиль
Дисциплина богата советами, которые повторяют как заповеди. Полезно понимать, за какими из них стоят исследования, за какими — только здравый смысл, а какие — просто мода.
| Утверждение | Статус | Основание |
|---|---|---|
| Плейсхолдер вместо подписи вредит | сильные данные | эксперименты NN/g и Baymard, требования WCAG к подписям |
| Ошибка рядом с полем работает лучше сводки наверху | сильные данные | исследования форм Baymard; сводка нужна дополнительно, не вместо |
| Преждевременная инлайн-валидация раздражает | сильные данные | тесты форм Baymard, повторяемый эффект |
| Люди не читают, а сканируют | сильные данные | eye-tracking NN/g, десятки исследований |
| Кнопка-глагол понятнее «ОК» | умеренные данные | тесты диалогов; частично закреплено в WCAG 2.5.3 |
| Объяснение «зачем нужны данные» снижает отказы | умеренные данные | эксперименты в чекаутах, эффект зависит от чувствительности поля |
| Юмор в ошибках повышает лояльность | нет данных, есть риск | часто вредит при высоком стрессе |
| «Чем короче, тем лучше» | миф | Джинни Редиш: важна не длина, а информационная плотность и структура |
| «Вы» против «ты», точка в конце подсказки | стиль | позиционирование бренда, а не юзабилити; ноль измеримого влияния |
| Заглавные буквы в кнопках | стиль, лёгкий минус | сплошной верхний регистр читается чуть медленнее, эффект мал |
Отсюда практическое следствие: спорные вопросы стиля решает контент-гайд волевым решением, а вопросы из верхних строк таблицы решают данные — и если кто-то в команде хочет их оспорить, это юзабилити-тест, а не совещание.
Отдельно про русскоязычную традицию «инфостиля». Приёмы вроде вычёркивания оценочных прилагательных и штампов действительно чистят текст. Но доведённое до догмы «сокращай» ломает интерфейс: из сообщения об ошибке первым делом вылетает объяснение причины, из подсказки — ответ на вопрос «зачем», и остаётся телеграфный стиль, в котором нет ни одной опоры. Сокращайте до тех пор, пока текст отвечает на вопрос пользователя, и ни словом дальше.
Числа, даты, единицы и множественное число
Место, где интерфейсный текст пересекается с кодом и где чаще всего проступает «программистский» акцент. Плюрализация: в русском три формы плюс форма для дробных: 1 файл, 2 файла, 5 файлов, 1,5 файла. Категории CLDR для русского — one, few, many, other; полная таблица правил — в спецификации CLDR. Никогда не собирайте строку конкатенацией — используйте ICU MessageFormat или встроенный Intl.PluralRules.
// Правила выбора формы отдаёт платформа, а не наши if-ы по последней цифре
const pr = new Intl.PluralRules("ru-RU");
// other — сюда попадают дробные: «1,5 файла»
const FILE_FORMS: Record<Intl.LDMLPluralRule, string> = {
zero: "файлов", one: "файл", two: "файла",
few: "файла", many: "файлов", other: "файла",
};
export function filesLabel(count: number): string {
const form = FILE_FORMS[pr.select(count)];
// Пробел-разделитель разрядов — тоже часть локали, не наша забота
return `${new Intl.NumberFormat("ru-RU").format(count)} ${form}`;
}
filesLabel(1); // «1 файл» filesLabel(3); // «3 файла»
filesLabel(17); // «17 файлов» filesLabel(1021); // «1 021 файл»
В ресурсном файле та же строка живёт как шаблон ICU — тогда переводчик на другой язык сам решает, сколько форм ему нужно:
{
"files.selected": "{count, plural, one {Выбран # файл} few {Выбрано # файла} many {Выбрано # файлов} other {Выбрано # файла}}"
}
Даты. Абсолютная дата нужна там, где человек будет сверяться с документом или календарём: «12 июля 2026». Относительная («2 часа назад») хороша в лентах и уведомлениях, но вредна в юридически значимых местах — в истории платежей нужна точная отметка. Компромисс — относительная дата в тексте и точная в подсказке при наведении, но помните: наведения нет на телефоне, значит, точная дата должна быть доступна и по тапу.
Единицы и деньги. Пишите единицу рядом с числом и в том же формате, что на чеке: «1 490 руб.», «5 МБ», «10–15 минут». Не смешивайте «Мб» и «МБ» — это разные величины, и в интерфейсах хранилищ это регулярно приводит к жалобам.
Числа в кнопках. «Удалить 12 файлов» безопаснее, чем «Удалить»: человек видит масштаб операции. Но подставлять число надо через плюрализацию, а не «Удалить 12 файл(ов)» — скобки в интерфейсе выглядят как расписка в том, что до текста никому не было дела.
Локализация: почему макет разъезжается
Даже если продукт одноязычный, правила локализации полезны — они защищают от длинных реальных данных. Текст меняет длину при переводе непредсказуемо: W3C приводит ориентировочные коэффициенты роста — короткие строки до 10 символов при переводе с английского могут вырасти в 2–3 раза, длинные абзацы — на 10–30% (Text size in translation). Русский относительно английского обычно длиннее на 15–30%, немецкий даёт рекордные составные слова. Кнопка «Save» превращается в «Сохранить», «Speichern», «Sauvegarder» — и вёрстка с фиксированной шириной ломается. Три практических следствия для дизайнера.
- Проверяйте макет на самой длинной реалистичной строке, а не на «Иван Иванов». Возьмите из базы настоящее длинное название компании, настоящий email на 40 символов, настоящий адрес.
- Никогда не склеивайте предложение из кусков. Такой код выглядит невинно и неперводим:
// ПЛОХО: порядок слов и согласование зашиты в конкатенацию
const msg = "Вы " + (isDeleted ? "удалили" : "изменили") + " " + count + " " + entity;
// ХОРОШО: полноценные ключи под каждый смысл
// events.deleted = "{count, plural, one {Удалён # проект} few {Удалено # проекта} many {Удалено # проектов} other {Удалено # проекта}}"
// events.updated = "{count, plural, one {Изменён # проект} few {Изменено # проекта} many {Изменено # проектов} other {Изменено # проекта}}"
const msg2 = t(isDeleted ? "events.deleted" : "events.updated", { count });
- Задайте правило поведения при переполнении для каждого текстового слота: перенос на вторую строку, усечение с многоточием (и обязательно полный текст в тултипе и в accessible name), уменьшение кегля. Это часть спецификации компонента в дизайн-системе, а не импровизация разработчика.
Как текст живёт в команде
Текст в макете — черновик. Настоящий текст живёт в репозитории, в ресурсных файлах, и меняется чаще, чем макет. Отсюда практика: у каждой строки есть ключ, владелец и контекст.
Ключевая идея схемы — в последних трёх шагах. Обратная связь от поддержки — самый дешёвый источник данных о тексте. Если в тикетах регулярно встречается «нажал, а что дальше — непонятно», у вас есть готовый список строк на переписывание, и он не требует ни одного исследования.
Формат передачи строк — обычная таблица, но с обязательными колонками:
# strings/checkout.yaml — источник истины, ревьюится в PR как код
checkout.card.label:
ru: "Номер карты"
context: "Подпись поля ввода на экране оплаты"
max_length: 24 # шире не влезет в мобильную колонку 320px
owner: "design"
checkout.card.hint:
ru: "Спишем {amount} один раз. Карту не сохраняем."
context: "Подсказка под полем, видна всегда, не только при ошибке"
vars: { amount: "сумма заказа с валютой, уже отформатирована" }
owner: "product"
checkout.card.error.length:
ru: "Нужно 16 цифр, сейчас {actual}"
context: "Показывается по blur; повторно — на каждый ввод"
vars: { actual: "число введённых цифр" }
owner: "design"
a11y: "Объявляется через role=alert, фокус остаётся в поле"
Такой файл можно проверять автоматически. Простой линтер ловит большую часть регрессий ещё до ревью:
"""Линтер интерфейсных строк: длина, запрещённые формулировки, потерянные переменные."""
import re, sys, yaml
BANNED = {
"что-то пошло не так": "нет причины и нет действия",
"произошла ошибка": "нет причины и нет действия",
"вы ввели неверно": "обвинение пользователя",
"нажмите здесь": "неинформативный текст ссылки, WCAG 2.4.4",
"попробуйте позже": "часто ложь: позже ничего не изменится",
}
VAR = re.compile(r"\{(\w+)\}")
def check(path: str) -> list[str]:
data = yaml.safe_load(open(path, encoding="utf-8"))
problems: list[str] = []
for key, item in data.items():
text, limit = item["ru"], item.get("max_length")
for phrase, why in BANNED.items():
if phrase in text.lower():
problems.append(f"{key}: запрещённая формулировка «{phrase}» — {why}")
if limit and len(text) > limit:
problems.append(f"{key}: {len(text)} символов при лимите {limit}")
declared, used = set(item.get("vars", {})), set(VAR.findall(text))
if declared != used: # переменная без описания или описание без переменной
problems.append(f"{key}: несовпадение переменных {sorted(used ^ declared)}")
if not item.get("context"):
problems.append(f"{key}: нет контекста — переводчик не поймёт, что это")
return problems
if __name__ == "__main__":
found = [p for path in sys.argv[1:] for p in check(path)]
print("\n".join(found) or "Строки в порядке")
sys.exit(1 if found else 0)
Сложность здесь линейная — O(n) по числу строк и O(1) дополнительной памяти на строку. Смысл не в алгоритме, а в том, что проверка встроена в CI: тексты перестают деградировать между релизами. Как встраивать такое в пайплайн — в треке devops и в статье про тестирование.
Передача в разработку и почему «пиксель в пиксель» — плохая цель
Тексты — самая наглядная иллюстрация того, почему требование «свёрстано должно быть точь-в-точь как в макете» вредит продукту. Макет показывает один сэмпл системы: одно значение переменной, одну длину имени, один язык, один размер шрифта, одну ширину экрана. Реальность — это множество состояний: имя на 42 символа, сумма в семь разрядов, перевод на немецкий, пользовательский зум 200%, узкий экран 320px, системный крупный шрифт на телефоне. Требовать пиксельного совпадения с одним сэмплом — значит требовать, чтобы все остальные случаи сломались.
Правильная цель передачи — совпадение правил, а не координат:
- иерархия (что главнее, что второстепенно) сохранена;
- отступы взяты из токенов системы, а не измерены линейкой;
- поведение при переполнении задано явно для каждого слота;
- состояния компонентов реализованы все, а не только «нормальное»;
- порядок фокуса и порядок чтения соответствуют смыслу.
Отсюда обязанности дизайнера в передаче — не «отдать красивый макет», а отдать спецификацию поведения:
| Что передаём | Зачем разработчику |
|---|---|
| Ключи строк, а не картинки с текстом | чтобы копировать без опечаток и класть в ресурсный файл |
| Все состояния каждого текстового слота | чтобы не выдумывать текст загрузки и ошибки самому |
| Правило переполнения для каждого слота | чтобы длинное имя не ломало карточку |
| Лимиты длины с обоснованием | чтобы понимать, откуда взялась цифра, а не спорить |
| Переменные и их формат | чтобы «12.07.2026» и «12 июля» не перепутались |
| Пометки по доступности | что объявлять, куда переводить фокус |
| Реальные данные для проверки | чтобы собрать экран на боевых значениях, а не на «Lorem» |
И симметрично — обязанности разработчика: не молчать, когда строка не влезает, а возвращать вопрос; не сокращать текст самостоятельно; не заменять сообщение об ошибке на e.message. Самый дешёвый ритуал, который снимает 80% споров, — совместный просмотр собранного экрана на реальных данных до релиза, минут на пятнадцать. Подробно о процессе — в передаче в разработку и карьере, а о том, как это выглядит со стороны фронтенда, — в статье «Формы и валидация».
Доступность текста на этапе дизайна
Полностью тема раскрыта в треке accessibility — обзор, семантика, скринридеры — а в следующей статье трека мы разберём её как этап проектирования. Здесь достаточно того, что относится именно к формулировкам.
Текст ссылки должен работать вырванным из контекста. Скринридер умеет зачитывать список всех ссылок страницы; список из восьми «Подробнее» бесполезен. Пишите «Подробнее об условиях доставки» — критерий 2.4.4 Link Purpose.
Видимая надпись входит в программное имя. Если на кнопке написано «Отправить», её aria-label не может быть «Отправить заявку на кредит» — голосовое управление не найдёт кнопку по видимому названию (критерий 2.5.3). И ошибка не может передаваться только цветом или иконкой: нужен текст (критерий 1.4.1) и, где возможно, подсказка, как исправить (критерий 3.3.3 Error Suggestion).
Статусы должны объявляться. Тост «Сохранено», который просто появился, для скринридера не существует. В макете рядом с тостом должна стоять пометка: «объявляется вежливо» или «объявляется немедленно». Это ваша ответственность как проектировщика: только вы знаете, важно ли прерывать чтение.
Простой язык — это доступность. GOV.UK ориентируется на понятность текста для читателя примерно девятилетнего уровня чтения и открыто публикует руководство по стилю со списком слов, которых стоит избегать. Это не про «упрощение для глупых»: под нагрузкой, в спешке и на маленьком экране сложный синтаксис не читает никто.
Как проверять текст
Текст проверяется дешевле, чем макет, и почти всегда — быстрее.
Тест на предсказание. Показываете экран, закрываете всё кроме кнопки и спрашиваете: «что произойдёт, если нажать?». Если из пяти человек трое ответили по-разному — название плохое, независимо от того, насколько оно вам нравится. Рядом стоит пятисекундный тест для заголовков и пустых экранов: показать на 5 секунд, спросить «что это за экран и что тут можно сделать».
Highlighter test. Даёте распечатку экрана и два маркера: зелёным отметить то, что помогает, розовым — то, что смущает или лишнее. Работает без модератора и мгновенно показывает, какие абзацы никто не читает.
Cloze-тест на понятность. Из текста удаляется каждое пятое слово, человек восстанавливает пропуски. Доля правильных восстановлений — прокси понятности; методика описана у NN/g. Нужна редко, но незаменима для юридических и медицинских текстов.
A/B-тест формулировок — мощный, но применим только там, где хватает трафика. На экране, который видят 200 человек в месяц, разница между «Скачать» и «Экспорт» никогда не наберёт статистической значимости; там работает юзабилити-тест на пяти пользователях. Подробнее — в юзабилити-тестировании, метриках UX и в статье про эксперименты трека продакт-менеджмента.
Метрики, по которым видно плохой текст:
- доля срабатываний валидации на конкретном поле (высокая — правило не объяснено заранее);
- отвал на конкретном шаге воронки при том, что технически всё работает;
- обращения в поддержку с формулировками «не понял», «не нашёл», «а это точно»;
- доля нажатий «Отмена» в подтверждающих диалогах — если она велика, диалог пугает не тем;
- время до первого клика на экране (косвенный признак того, что человек читает и не находит).
Типичные ошибки
- Текст пишут в последний день. Как следствие — в макете «Lorem ipsum», а в проде — то, что придумал разработчик в спешке.
- Кнопка называется механикой («Отправить», «Применить»), а не результатом.
- Подпись поля спрятана в плейсхолдер ради «чистоты» макета.
- Требования показываются только после ошибки.
- Сообщение об ошибке не говорит, что делать, и не говорит, что стало с данными.
- Код исключения показан вместо объяснения, а идентификатор для поддержки — не показан вовсе; «Что-то пошло не так» стоит на всех случаях жизни, включая нехватку прав.
- Юмор в местах с высокой ценой ошибки.
- Один и тот же объект называется разными словами на разных экранах (в списке «заявка», в письме «обращение», в поддержке «тикет»).
- Строки склеиваются конкатенацией — перевод и плюрализация ломаются; макет проверен на коротком имени, а в проде имена длинные.
- Тост с важной информацией исчезает через две секунды.
- Правовые формулировки переписаны дизайнером «чтобы звучало проще» — и потеряли юридический смысл.
- Спор о заглавной букве в «Вы» съел ревью, на котором можно было разобрать сообщения об ошибках.
Мини-итог
- Интерфейс — это в основном текст; текст проектируют, а не «дописывают».
- Каждый слот отвечает на один конкретный вопрос пользователя; слот без вопроса — лишний.
- Кнопка называется результатом, а не операцией; словарь берётся у пользователя, а не из кода.
- Ограничения показываются до ввода; валидация срабатывает после потери фокуса, а потом — на каждый символ.
- Сообщение об ошибке состоит из четырёх частей: что случилось, почему, что делать, идентификатор для поддержки. Плюс судьба данных.
- Тон — функция ситуации, а не только бренда: чем выше стресс, тем суше формулировка.
- Есть вещи, доказанные исследованиями (плейсхолдеры, момент валидации, расположение ошибок), и есть чистая вкусовщина — её закрывают гайдом, а не спорами.
- Числа, даты и множественное число форматирует платформа, а не наши
if-ы; строки не склеиваются из кусков. - Передача в разработку — это ключи, состояния, лимиты и правила переполнения. «Пиксель в пиксель» — плохая цель, потому что макет показывает один сэмпл, а продукт живёт во множестве.
Источники
- Jakob Nielsen. How Little Do Users Read? и F-Shaped Pattern — как на самом деле читают интерфейсы.
- NN/g. Error Message Guidelines, Placeholders in Form Fields Are Harmful, Four Dimensions of Tone of Voice, Cloze Test.
- Baymard Institute. Inline Form Validation — момент срабатывания важнее самого факта проверки.
- Ginny Redish. Letting Go of the Words — базовая книга про содержательный, а не «короткий» текст; Torrey Podmajersky. Strategic Writing for UX — про связь текста с целями продукта; Kinneret Yifrah. Microcopy: The Complete Guide — разбор слотов и формулировок.
- GOV.UK Style Guide и Content Design — эталон простого языка в государственных сервисах.
- Публичные контент-гайды: Mailchimp, Shopify Polaris, Material Design, Apple HIG: Writing, Microsoft Writing Style Guide.
- Unicode CLDR Plural Rules, ICU MessageFormat, W3C Text size in translation.
- W3C. WCAG 2.2 Understanding — критерии 1.4.1, 2.4.4, 2.5.3, 3.3.1, 3.3.3.
Что дальше
Мы всё время упирались в то, что текст должен дойти до человека — быть видимым, объявленным, доступным с клавиатуры и понятным при увеличении. Следующая статья превращает это в проектную дисциплину: как учитывать доступность на этапе дизайна, а не в конце.
Доступность на этапе дизайна: контраст, размеры, состояния, порядок