# Don't Ship the Schema

**MUST** · **ID:** `content-dont-ship-schema` · **Category:** content
**Source:** [Vercel](https://github.com/vercel-labs/agent-skills/blob/main/skills/web-design-guidelines/SKILL.md)
**Interactive version:** https://ui-guides-agent-rules.netlify.app/principles/content-dont-ship-schema

> MUST: Don't ship the schema—visuals may omit labels but accessible names still exist

Keep accessible names even when the visual design omits visible labels

A dense or minimal layout is free to drop visible labels — a search field can rely on a placeholder, an action row can be icon-only. What it cannot drop is the underlying schema: every control still needs an accessible name in the tree. A placeholder is not a name (it disappears on input and is not reliably announced), and an icon is not a name. Use a visually-hidden `<label>` or `aria-label`, and mark the icon `aria-hidden`. The visual design is a projection of the schema, not a replacement for it.

## Bad — do not do this

`content-dont-ship-schema-bad`

```tsx
import { Search, Star, Trash2 } from 'lucide-react';
import { ScreenReaderView } from '@/components/demo-kit/ScreenReaderView';

export function DontShipSchemaBad() {
  return (
    <div className="w-full max-w-sm space-y-4">
      <ScreenReaderView>
        <div className="bg-card border border-border rounded-lg p-4 space-y-3">
          {/* The visual design is fine. Nothing carries an accessible name. */}
          <div className="flex items-center gap-2">
            <input
              placeholder="Search projects"
              className="flex-1 min-w-0 px-3 py-2 bg-background border border-border rounded-md text-sm text-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring"
            />
            <button className="p-2 bg-muted rounded-md focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring">
              <Search className="w-4 h-4 text-foreground" />
            </button>
            <button className="p-2 bg-muted rounded-md focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring">
              <Star className="w-4 h-4 text-foreground" />
            </button>
            <button className="p-2 bg-muted rounded-md focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring">
              <Trash2 className="w-4 h-4 text-foreground" />
            </button>
          </div>
        </div>
      </ScreenReaderView>

      <p className="text-xs text-error">
        Turn on the emulation: the field and all three buttons announce with no name — one of them deletes, and a
        screen reader user hears “button, button, button” and has to guess which.
      </p>
    </div>
  );
}
```

## Good — do this

`content-dont-ship-schema-good`

```tsx
import { Search, Star, Trash2 } from 'lucide-react';
import { ScreenReaderView } from '@/components/demo-kit/ScreenReaderView';

export function DontShipSchemaGood() {
  return (
    <div className="w-full max-w-sm space-y-4">
      <ScreenReaderView>
        <div className="bg-card border border-border rounded-lg p-4 space-y-3">
          {/* Identical visual design — the labels exist, they are just not painted. */}
          <div className="flex items-center gap-2">
            <label htmlFor="q" className="sr-only">
              Search projects
            </label>
            <input
              id="q"
              placeholder="Search projects"
              className="flex-1 min-w-0 px-3 py-2 bg-background border border-border rounded-md text-sm text-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring"
            />
            <button
              aria-label="Search"
              className="p-2 bg-muted rounded-md focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring"
            >
              <Search className="w-4 h-4 text-foreground" aria-hidden="true" />
            </button>
            <button
              aria-label="Add to favorites"
              className="p-2 bg-muted rounded-md focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring"
            >
              <Star className="w-4 h-4 text-foreground" aria-hidden="true" />
            </button>
            <button
              aria-label="Delete project"
              className="p-2 bg-muted rounded-md focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring"
            >
              <Trash2 className="w-4 h-4 text-foreground" aria-hidden="true" />
            </button>
          </div>
        </div>
      </ScreenReaderView>

      <p className="text-xs text-success">
        Same pixels, complete schema — turn on the emulation and every control announces its name, even though the
        labels aren’t drawn.
      </p>
    </div>
  );
}
```

## References

- [Vercel Web Interface Guidelines](https://github.com/vercel-labs/web-interface-guidelines)
- [MDN: Accessible name](https://developer.mozilla.org/en-US/docs/Glossary/Accessible_name)
- [WCAG 2.1: Name, Role, Value](https://www.w3.org/WAI/WCAG21/Understanding/name-role-value.html)
