EthicsComputerCourses
Join training +91 90532 10052

UX Writing

UI/UX Design · Lesson 23 of 30 Advanced 6 min read Updated

By the end of this lesson you will be able to write button labels, error messages, confirmations and empty states that are short, clear and kind, and format Indian numbers and dates correctly.

UX writing is writing the words inside a product: buttons, labels, headings, hints, errors and messages. These small pieces of text are often called microcopy. They look minor, but people read them at the exact moment they decide what to do. One unclear word on a Pay button can stop a payment.

Four qualities of good interface text

  1. Clear: plain, everyday words. Say "Pay", not "Initiate transaction".
  2. Concise: as few words as possible, but not fewer. Cut filler, keep meaning.
  3. Useful: tells people what to do next or what just happened.
  4. Consistent: the same thing always has the same name. If it is "Visit" on one screen, it is not "Appointment" on the next and "Booking" on a third.

Think of a good railway announcement: "Train 12015 to Ajmer will depart from platform 4 at 6:10." It is short, it leads with what matters, and it tells you what to do. Interface text should sound like that, not like a legal notice.

Buttons: say what happens

A button label should describe the action, usually starting with a verb. The person should know what happens before tapping.

VagueClear
SubmitBook visit
OKCancel visit
Yes / NoDelete report / Keep report
Click hereDownload prescription
ContinuePay ₹600

Avoid "Yes" and "No" in confirmations. People often skim the question, so the buttons themselves must make sense alone.

Run this example. Toggle between the before and after versions of the same cancel dialog.

HTMLTry it yourself
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Before and after copy</title>
  <style>
    body { font-family: system-ui, sans-serif; padding: 16px; color: #1a1a2e; max-width: 380px; }
    .dialog { border: 1px solid #d4d4dc; border-radius: 12px; padding: 20px; margin-top: 12px; }
    .dialog h2 { font-size: 18px; margin: 0 0 8px; }
    .dialog p { margin: 0 0 16px; color: #374151; }
    .row { display: flex; gap: 8px; }
    .row button { flex: 1; min-height: 44px; border-radius: 8px; font-size: 15px; font-weight: 600; }
    .a { background: #fff; border: 2px solid #5b21b6; color: #3b0d8c; }
    .b { background: #b91c1c; border: 2px solid #b91c1c; color: #fff; }
  </style>
</head>
<body>
  <label><input type="checkbox" id="after"> Show improved copy</label>
  <div class="dialog" role="group" aria-labelledby="title">
    <h2 id="title"></h2>
    <p id="body"></p>
    <div class="row"><button class="a" type="button" id="a"></button><button class="b" type="button" id="b"></button></div>
  </div>
  <script>
    const copy = {
      before: { title: 'Warning!', body: 'Are you sure you want to proceed with the cancellation of the appointment? This action is irreversible.', a: 'No', b: 'Yes' },
      after: { title: 'Cancel your visit with Dr Mehta?', body: 'Your Thu 19 Nov, 9:00 am slot will go to another patient. You can book a new time later.', a: 'Keep visit', b: 'Cancel visit' }
    };
    function show(version) {
      const c = copy[version];
      ['title', 'body', 'a', 'b'].forEach(k => document.getElementById(k).textContent = c[k]);
    }
    document.getElementById('after').addEventListener('change', e => show(e.target.checked ? 'after' : 'before'));
    show('before');
  </script>
</body>
</html>

You should first see a dialog titled "Warning!" with "No" and "Yes". Tick the box. Now the title asks the real question, the body explains the consequence in plain words, and the buttons say exactly what each choice does.

Headings and front-loading

People scan. Put the most important words at the start of headings, labels and list items. This is called front-loading.

  • Weak: "In case you need them, your lab reports can be found here"
  • Better: "Lab reports"
  • Weak: "Please note that the clinic will be closed on Sunday"
  • Better: "Closed on Sunday, 22 Nov"

Errors, empty states and success

You practised errors in Forms & Error States. The same rules apply everywhere:

  • Errors: say what happened, why if useful, and how to fix it. Never blame the user. "We could not send the OTP. Check your network and try again." beats "OTP failure".
  • Empty states: explain what will appear and offer the next action.
  • Success: confirm what happened and what comes next. "Visit booked for Thu 19 Nov, 9:00 am. We have sent an SMS with directions."

Voice and tone

Voice is the product's personality, which stays the same everywhere: for a clinic, perhaps calm, warm and respectful. Tone changes with the situation, the way a doctor speaks differently to a nervous patient than to a colleague.

  • Celebrating a booking: warm and brief.
  • A failed payment: calm, clear, reassuring about money. "Your payment did not go through. No money was deducted."
  • A medical report: neutral and precise. No jokes, no exclamation marks.

Numbers, money and dates for India

Format numbers the way readers expect. Indian English groups large numbers in lakhs and crores: 1,25,000 rather than 125,000. Browsers can do this for you with the Intl API and the en-IN locale.

HTMLTry it yourself
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Indian formats</title>
  <style>
    body { font-family: system-ui, sans-serif; padding: 16px; color: #1a1a2e; }
    td, th { padding: 6px 12px; text-align: left; border-bottom: 1px solid #e5e7eb; }
  </style>
</head>
<body>
  <table>
    <thead><tr><th>Value</th><th>en-IN</th><th>en-US</th></tr></thead>
    <tbody id="rows"></tbody>
  </table>
  <script>
    const visit = new Date(2026, 10, 19, 9, 0);
    const formats = [
      ['Amount', l => new Intl.NumberFormat(l, { style: 'currency', currency: 'INR', maximumFractionDigits: 0 }).format(125000)],
      ['Large amount', l => new Intl.NumberFormat(l, { style: 'currency', currency: 'INR', maximumFractionDigits: 0 }).format(12500000)],
      ['Date', l => new Intl.DateTimeFormat(l, { weekday: 'short', day: 'numeric', month: 'short', year: 'numeric' }).format(visit)],
      ['Time', l => new Intl.DateTimeFormat(l, { hour: 'numeric', minute: '2-digit' }).format(visit)]
    ];
    const rows = document.getElementById('rows');
    formats.forEach(([name, fn]) => {
      const tr = document.createElement('tr');
      [name, fn('en-IN'), fn('en-US')].forEach(text => {
        const td = document.createElement('td');
        td.textContent = text;
        tr.append(td);
      });
      rows.append(tr);
    });
  </script>
</body>
</html>

You should see a table where the en-IN column shows ₹1,25,000 and ₹1,25,00,000, a date like "Thu, 19 Nov, 2026" and "9:00 am", while the en-US column groups digits in thousands and puts the month first. Write dates with the month as a word ("19 Nov") so nobody confuses 04/05 as April or May.

Sentence case and inclusive language

  • Use sentence case ("Book a visit"), not Title Case Everywhere. It is easier to read and less shouty.
  • Use people-first, neutral language. Say "people with low vision", not stereotypes. Avoid gendered defaults like "he" for the user.
  • Say "you" for the user and "we" for the product. Keep it consistent.

Common mistakes

  • Generic buttons: Submit, OK, Yes, No.
  • Jargon: "Authenticate", "Invalid credentials", "TPA".
  • Blaming text: "You entered a wrong number."
  • Inconsistent names for the same thing.
  • Hiding consequences: not saying what will happen after a destructive action.

Key points

  • UX writing is clear, concise, useful and consistent.
  • Buttons describe the action, usually with a verb, and make sense on their own.
  • Front-load headings and labels with the most important words.
  • Voice stays the same; tone adapts to the situation.
  • Use Indian formats (lakh grouping, ₹, "19 Nov") and the en-IN locale.
  • Write for translation: no idioms, no text in images, room to grow.

Exercise

Rewrite these messages for the clinic app: "Error: session timeout", "Submit", "Your request has been successfully processed", and "No data found" on the saved doctors screen. Then, in the formats example, add a row that shows 4,999 rupees with two decimal places.

Show solution
JavaScript
['Fee with paise', l => new Intl.NumberFormat(l, { style: 'currency', currency: 'INR', minimumFractionDigits: 2 }).format(4999)],

The new row prints ₹4,999.00 in both locales, since grouping only differs above one lakh. Rewrites: "You were signed out to keep your account safe. Sign in again to continue.", "Book visit", "Visit booked for Thu 19 Nov at 9:00 am", and "No saved doctors yet. Tap the heart on a doctor's profile to save them here."

Quick Quiz

1. Which button label is best for a destructive confirmation?

2. What does front-loading mean?

3. How does the en-IN locale format 125000 rupees?

Learn UI/UX Design with a mentor

Free lessons get you started. Our UI/UX Design training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call