# Four Options at Any Decision Point

**SHOULD** · **ID:** `interactions-impeccable-four-option-limit` · **Category:** interactions
**Source:** [impeccable](https://impeccable.style/)
**Interactive version:** https://ui-guides-agent-rules.netlify.app/principles/interactions-impeccable-four-option-limit

> SHOULD: Cap any decision point at <=4 simultaneously visible options (Miller's Law as revised by Cowan, 2001: working memory holds ~4 items). 5-7 is the boundary — group them or reveal progressively; 8+ is overload, and an overloaded user does not choose slowly, they skip, misclick, or abandon. Carried into surfaces: <=5 top-level nav items, <=4 form fields before a visual break, 1 primary + 1-2 secondary buttons (rest in a menu), <=4 key metrics above the fold, <=3 pricing tiers. Grouping does not remove capability — a 4-item nav with a "More" group still reaches nine destinations — it removes them from the decision.

Working memory holds about four items — cap visible choices, nav items, and pricing tiers accordingly

The thresholds are explicit and they are not aesthetic: at any decision point, count the distinct options a user must simultaneously consider. Four or fewer sits inside working memory. Five to seven is the boundary — group them or reveal them progressively. Eight or more is overload, and an overloaded user does not choose slowly, they skip, misclick, or abandon. impeccable carries the rule into specific surfaces: navigation menus get at most 5 top-level items (group the rest under clear categories), form sections at most 4 fields before a visual break, action buttons get 1 primary and 1–2 secondary with the rest in a menu, dashboards at most 4 key metrics above the fold, and pricing at most 3 tiers, because more causes analysis paralysis. The counter-intuitive part is that grouping does not remove capability — a nav with 4 items plus a "More" group still reaches nine destinations. It removes them from the decision, which is the only place they were doing harm. Pair a capped tier list with a highlighted recommendation and you collapse the choice further still: the question stops being "compare three" and becomes "accept this one, or look at the other two".

## Bad — do not do this

`interactions-impeccable-four-option-limit-bad`

```tsx
/**
 * Bad: 9 top-level nav items and 6 pricing tiers.
 *
 * Working memory holds about 4 items. At 8+ the set cannot be held at all, so
 * the user stops comparing and starts skipping — and a user who cannot compare
 * picks nothing.
 */
const NAV = [
  'Products',
  'Solutions',
  'Platform',
  'Developers',
  'Resources',
  'Customers',
  'Partners',
  'Company',
  'Pricing',
];

const TIERS = [
  { name: 'Free', price: '$0' },
  { name: 'Starter', price: '$9' },
  { name: 'Basic', price: '$19' },
  { name: 'Plus', price: '$39' },
  { name: 'Pro', price: '$79' },
  { name: 'Business', price: '$149' },
];

export function ImpeccableFourOptionLimitBad() {
  return (
    <div className="w-full space-y-4">
      <nav aria-label="Main (overloaded)" className="rounded-lg border border-border bg-card p-3">
        <ul className="flex flex-wrap gap-x-3 gap-y-2">
          {NAV.map((item) => (
            <li key={item}>
              <a
                href="#"
                className="rounded-md px-1 py-0.5 text-xs text-foreground underline focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring"
              >
                {item}
              </a>
            </li>
          ))}
        </ul>
        <p className="mt-2 text-xs text-error">9 top-level items &mdash; the cap is 5.</p>
      </nav>

      <div className="grid grid-cols-3 gap-2">
        {TIERS.map((tier) => (
          <div key={tier.name} className="rounded-lg border border-border bg-card p-3">
            <p className="text-xs font-semibold text-foreground">{tier.name}</p>
            <p className="text-sm text-foreground">{tier.price}</p>
            <button className="mt-2 w-full rounded-md border border-border px-2 py-1 text-xs text-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring">
              Choose
            </button>
          </div>
        ))}
      </div>

      <p className="text-xs text-error">
        6 tiers, none recommended. Nine nav items and six near-identical plans both land in the 8+
        band where users skip, misclick, or abandon &mdash; there is no way to hold the set in mind
        long enough to choose.
      </p>
    </div>
  );
}
```

## Good — do this

`interactions-impeccable-four-option-limit-good`

```tsx
import { useState } from 'react';

/**
 * Good: 4 visible nav items with the remainder grouped under one, and 3 pricing
 * tiers with a recommendation.
 *
 * Grouping does not delete the options — it removes them from the decision. The
 * user holds four things, not nine, and the highlighted tier collapses the
 * pricing decision from "compare three" to "accept one or look at two".
 */
const GROUPED = ['Customers', 'Partners', 'Company', 'Careers', 'Blog'];

const TIERS = [
  { name: 'Starter', price: '$9', blurb: 'Solo projects', recommended: false },
  { name: 'Pro', price: '$39', blurb: 'Growing teams', recommended: true },
  { name: 'Business', price: '$149', blurb: 'Org-wide', recommended: false },
];

export function ImpeccableFourOptionLimitGood() {
  const [moreOpen, setMoreOpen] = useState(false);

  return (
    <div className="w-full space-y-4">
      <nav aria-label="Main" className="rounded-lg border border-border bg-card p-3">
        <ul className="flex flex-wrap items-center gap-x-4 gap-y-2">
          {['Product', 'Developers', 'Pricing', 'Docs'].map((item) => (
            <li key={item}>
              <a
                href="#"
                className="rounded-md px-1 py-0.5 text-xs text-foreground underline focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring"
              >
                {item}
              </a>
            </li>
          ))}
          <li className="relative">
            <button
              onClick={() => setMoreOpen((v) => !v)}
              aria-expanded={moreOpen}
              className="rounded-md border border-border px-2 py-0.5 text-xs text-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring"
            >
              More
            </button>
            {moreOpen && (
              <ul className="mt-2 space-y-1 rounded-md border border-border bg-muted p-2">
                {GROUPED.map((item) => (
                  <li key={item}>
                    <a
                      href="#"
                      className="rounded-md px-1 py-0.5 text-xs text-muted-foreground underline focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring"
                    >
                      {item}
                    </a>
                  </li>
                ))}
              </ul>
            )}
          </li>
        </ul>
        <p className="mt-2 text-xs text-muted-foreground">
          4 items plus one group &mdash; comfortably under the 5-item cap.
        </p>
      </nav>

      <div className="grid grid-cols-3 gap-2">
        {TIERS.map((tier) => (
          <div
            key={tier.name}
            className={`rounded-lg border bg-card p-3 ${
              tier.recommended ? 'border-primary' : 'border-border'
            }`}
          >
            {tier.recommended && (
              <p className="mb-1 text-[0.625rem] font-semibold uppercase tracking-wide text-primary">
                Recommended
              </p>
            )}
            <p className="text-xs font-semibold text-foreground">{tier.name}</p>
            <p className="text-sm text-foreground">{tier.price}</p>
            <p className="mt-0.5 text-xs text-muted-foreground">{tier.blurb}</p>
            <button
              className={`mt-2 w-full rounded-md px-2 py-1 text-xs focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring ${
                tier.recommended
                  ? 'bg-primary text-primary-foreground'
                  : 'border border-border text-foreground'
              }`}
            >
              Choose
            </button>
          </div>
        ))}
      </div>

      <p className="text-xs text-success">
        3 tiers, one recommended. Both decision points now sit inside the &le;4 working-memory band,
        and the nine nav destinations still exist &mdash; they just are not competing for attention
        at the moment of choice.
      </p>
    </div>
  );
}
```

## References

- [impeccable.style](https://impeccable.style/)
- [pbakaus/impeccable](https://github.com/pbakaus/impeccable)
- [Laws of UX — Miller's Law](https://lawsofux.com/millers-law/)
