Using #description for help textfor Drupal 11 , 10 , 9 , and 8

Last updated :  

A well-chosen label tells a user what a field is; #description tells them how to fill it out correctly — the format expected, a constraint not obvious from the field alone, or context about what happens with what they enter. It's genuinely optional, but the difference between a form people fill out correctly the first time and one that generates support tickets.

What you'll learn in this lesson

  • Where #description renders, and how that differs from a placeholder
  • Writing genuinely useful description text, not restating the obvious
  • HTML in descriptions, and the safe way to include it

A basic description

$form['api_key'] = [
  '#type' => 'textfield',
  '#title' => $this->t('API key'),
  '#description' => $this->t('Find this in your account settings under "Developer Access."'),
];

Unlike a placeholder (which disappears the moment a user starts typing, from the Text Fields lesson), the description renders as persistent visible text below the field — always visible, whether the field is empty or filled, which matters for genuinely important context a user might need to reference while filling out the field, not just before they start.

Write descriptions that add information, not restate the label. "Enter your name" as a description under a field titled "Name" tells the user nothing new. A genuinely useful description explains something not already obvious: a required format ("Use the format XXX-XXX-XXXX"), a consequence ("This will be publicly visible on your profile"), or a constraint ("Must be between 8 and 64 characters"). If a description would just repeat the label, leave it out entirely.

Safe HTML in descriptions

$form['password'] = [
  '#type' => 'password',
  '#title' => $this->t('New password'),
  '#description' => $this->t('Must be at least 8 characters and include a number. Password tips.', [
    ':url' => Url::fromRoute('mymodule.password_help')->toString(),
  ]),
];

$this->t() allows a specific safe subset of HTML tags (including <a>) directly in the translatable string, with placeholder substitution for the actual URL — never concatenate raw HTML strings yourself outside this pattern, since that reintroduces the exact escaping risks t() exists to prevent.

Quick check: a required field titled "Email" has the description "Enter your email address." Is this description adding real value? If you said no — it just restates what the label and #required indicator already communicate, without adding any new information like a format requirement or explanation — you've understood the actual bar this lesson sets for a description worth writing.

Key takeaways

  • #description renders as persistent visible text, unlike a placeholder which disappears once typing starts.
  • Write descriptions that add real information — a format, a consequence, a constraint — not a restatement of the label.
  • Use $this->t()'s safe-HTML-subset support for links or emphasis in a description, never raw string concatenation.

Coming up next

Description helps a user fill a field in. Next, the opposite: preventing input entirely with #disabled.