Disable Unavailable Variants Before the Click: Rule PDP-008
To disable unavailable product variants, render every size and colour option on the product page, grey out the combinations that cannot be bought, mark them as disabled in the code, and never wait for a click to reveal the problem. Rule PDP-008 in the UXFix audit fails any store that shows an error toast or message after the shopper picks a combination that was never available. The shopper should see the constraint before they act, not after.
What rule PDP-008 checks
PDP-008 is one of the product page rules in the 115-rule book we audit against. Its one-line definition is: unavailable variant combinations are disabled before selection, not reported as an error after selection. It is tagged high severity, decision stage, flow category, because it sits at the exact moment a shopper commits to a specific item.
The auditor loads a product with two or more variant axes, typically size and colour. It picks an option on the first axis and watches what happens to the second. Then it flips the order, picking the second axis first, and watches the first.
For a pass, four things need to be true on every combination that cannot be bought:
- The option is still visible, so the shopper knows it exists in the range.
- The option looks different from available options, using more than a small colour shift.
- The option is disabled in the DOM, with a
disabledoraria-disabled="true"attribute, so keyboard users and assistive tech get the same information as mouse users. - The reason is stated somewhere the shopper can find it, such as a "Sold out" label, a tooltip, or a message under the selector.
A store fails if any combination can be selected and then produces an error, whether that error is a toast, an inline message, a disabled add to cart button with no explanation, or a silent switch to a different variant. A store is partially affected if the options are visually greyed but still clickable, or disabled in code but not visibly, or handled on one axis but not the other.
Why does error-on-click cost you the sale?
The shopper arrives with a plan. They want the blue one in a medium. When the swatches all look identical, they assume every cell in the grid is for sale, and they act on that assumption.
An error after the click destroys the plan and hands them the job of rebuilding it. They now have to work out whether medium is gone in every colour or just in blue, whether the blue exists at all, and which of the remaining cells they can actually buy. On a six-size by four-colour product that is 24 combinations to probe by hand.
Error prevention is not a new idea. It is one of Nielsen's ten usability heuristics, and the guidance is to remove error-prone conditions before the user acts rather than reporting them afterwards. Baymard's product page research lands in the same place for variant selection: keep the option visible, make it obviously unavailable, and do not make the shopper discover it by trial.
On mobile the cost is worse. Toasts are transient, so a shopper who glances away misses the message and taps add to cart on a variant that never registered. They end up in the cart with the wrong item or nothing at all, and that is the point where we see them leave. We cover the broader pattern in why shoppers leave your product page without buying.
There is also a stock signal you lose. A shopper who cannot see that medium blue exists has no reason to ask you to notify them when it returns. Greyed out options keep that door open, which is the same logic behind our advice on out-of-stock pages.
Failing, partially affected, passing: what each looks like
We grade this rule from what the auditor can observe in the rendered page and the DOM. Below are the three states with the exact behaviour that puts a store in each.
Failing
In a failing store, the shopper sees a full row of size buttons in the same style. Nothing distinguishes the sizes you have from the sizes you do not. Only after clicking M does an error appear, and often the M button even shows a selected state while the message tells them it is not for sale.
We also fail three variants of this pattern. The first is the deferred error, where every option is selectable and the problem only surfaces when the shopper hits add to cart. The second is the silent substitution, where clicking M in Red quietly switches the colour to Black without telling anyone. The third is the dead button, where add to cart just goes grey with no reason given.
What the auditor records: click on M/Red returns an error toast, the M element has no disabled or aria-disabled attribute, the M element is focusable and reachable by keyboard, and the same error is reproducible from either axis order.
Partially affected
The partial state is the most common thing we see in audits. The store has done the visual half of the job and skipped the rest. The unavailable sizes look lighter, so a sighted mouse user gets a hint, but the element is still a live button.
Clicking it either selects it and then errors, which is the fail behaviour in disguise, or does nothing, which leaves the shopper guessing whether the page is broken. Keyboard users tab straight onto it because nothing in the accessibility tree says it is inactive. Screen reader users hear "M, button" with no indication it is sold out.
We also mark stores partial when the disabled state is correct on one axis and missing on the other. A store that greys out sizes after a colour is picked, but lets you pick any colour after a size is picked, has only solved half the grid. Same result if the disabled options are correct but there is no label, tooltip or note anywhere telling the shopper what the greyed state means.
What the auditor records: opacity or colour change present, element still focusable and clickable, no disabled attribute, and either no explanation or an explanation on only one axis.
Passing
In a passing store the constraint is visible before the shopper acts. Unavailable sizes stay in the row so the range is understood, but a strikethrough or hatched pattern makes them unmistakably different from the available ones. Hovering or focusing one shows "Sold out in Red", and a note under the row repeats it in plain text.
The element carries aria-disabled="true", and clicking it does not select it or fire an error. The rest of the grid stays reachable: the shopper can switch to Black and see M light back up, or keep M selected and see which colours are struck through. Both axes update each other.
All six sizes look identical. Shopper clicks M, gets a toast saying the combination is unavailable, and has to try other sizes one by one.
M and XL are struck through and disabled as soon as Red is chosen. A note says which sizes are sold out in Red and offers a back-in-stock alert. Shopper picks L or switches colour in one move.
What the auditor records: unavailable options visible and visually distinct, disabled or aria-disabled present, click produces no selection and no error, explanation present, behaviour symmetrical across axes.
- Keep sold-out options in the selector so the shopper knows the size or colour exists.
- Use a strikethrough, hatching or explicit label, not just a lighter shade.
- Set disabled or aria-disabled="true" so keyboard and screen reader users get the same state.
- Recalculate availability on both axes every time either one changes.
- Let a greyed option remain clickable and then throw an error.
- Wait until add to cart to reveal that the combination does not exist.
- Silently switch the shopper to a different colour or size.
- Remove a size from the list entirely and leave the shopper wondering if you ever stocked it.
Hide, disable, or error: which should you pick?
Store owners ask us this constantly, usually because their platform defaults to one of the three. Here is how each option scores and what it costs.
| Approach | PDP-008 result | Shopper experience | AI agent experience |
|---|---|---|---|
| Show all, error on click | Fail | Trial and error, toasts missed on mobile | Gets an unstructured error, often cannot recover |
| Hide unavailable options | Pass | No errors, but no way to tell the size exists or ask to be notified | Sees only buyable options, cannot report the wanted one as out of stock |
| Grey out only, still clickable | Partial | Hint for sighted mouse users, nothing for keyboard or screen reader | May still click it, since the DOM says it is active |
| Grey out, disable in code, label | Pass | Constraint visible before acting, notify-me path open | Reads disabled state, skips it, reports out of stock |
Hiding does pass the rule. We still recommend against it for any product where the missing option is temporary, because you lose the notify-me capture and the shopper cannot distinguish "sold out" from "never made". Reserve hiding for options that are permanently discontinued.
On the build side, the pattern is short. Each option needs a visual state and a programmatic state that agree with each other, plus a text reason.
<button
type="button"
class="variant-option variant-option--unavailable"
aria-disabled="true"
aria-describedby="size-m-note"
>M</button>
<span id="size-m-note" class="visually-hidden">Sold out in Red</span>Using aria-disabled rather than the native disabled attribute keeps the option focusable, so a screen reader user can land on it and hear why it is unavailable. Either approach passes PDP-008 as long as the click does nothing and the state is announced. If you use native disabled, put the explanation in visible text under the row so it is not lost to users who cannot focus the element.
One detail on contrast: WCAG 2.1 success criterion 1.4.3 exempts inactive controls from the minimum contrast ratio. That is permission to make the option look inactive, not permission to make it illegible. A shopper still has to read the letter M on a struck-through button to know which size is gone, so keep the text readable and let the strikethrough or hatching carry the disabled meaning.
Finally, availability must be recomputed on every change on every axis. The common bug is a page that disables sizes when colour changes but not colours when size changes.
Test both orders, and test the case where the shopper picks an available combination, then changes one axis to something that makes the other selection invalid. The invalid selection should clear or the page should say what happened, never silently substitute.
How AI shopping agents handle unavailable variants
An AI shopping agent driving your product page works from two inputs: the rendered DOM and whatever structured data you expose. It does not have a shopper's patience for trial and error, and it does not read toasts the way a person does.
When the agent hits an error-on-click store, the sequence is bad. It reads the size row, sees six active buttons, clicks M because that is what the user asked for, and then either misses the transient toast entirely or receives a text string it has to interpret.
Many agents proceed to add to cart, get a second error or a wrong variant, and abandon the task or report a failure back to the user. We see the same class of blocker across the funnel in why AI shopping agents abandon checkout on your store.
When the agent hits a passing store, the sequence is clean. It reads the size row, sees M carries aria-disabled="true" and a description of "Sold out in Red", skips it, and either picks the nearest available size or tells the user that M in Red is out of stock and offers L or Black. That is a recoverable state, and recoverable states are what get your store recommended rather than skipped.
Structured data helps the agents that read it before they ever click. If each variant is its own Offer in your schema.org markup with its own availability value, an agent can rule out sold-out combinations without touching the selector at all.
{
"@type": "Offer",
"sku": "TEE-RED-M",
"availability": "https://schema.org/OutOfStock",
"price": "29.00",
"priceCurrency": "GBP"
}The markup does not replace the disabled state on the page. The two need to agree, because an agent that reads InStock in the data and then finds the option disabled in the DOM will not trust either. Keep the availability feed that drives the selector and the one that drives the markup pointed at the same inventory source.
Frequently asked questions
Should out of stock variants be hidden, disabled, or shown with an error?
Disabled, with a visible label, for anything that might come back. The shopper learns the option exists, sees immediately that it is unavailable in the current combination, and can ask to be notified or switch to an available combination in a single move. Hiding passes PDP-008 but throws away the back-in-stock signal and leaves shoppers unsure whether you ever stocked the size. Showing an error after the click fails the rule outright, because it forces the shopper to discover your inventory by trial.
Does greying out a swatch count as disabling it?
Not on its own. A lighter shade tells a sighted mouse user something is different but tells a keyboard user, a screen reader user or an AI agent nothing, because the element is still active in the DOM. To pass, the option needs disabled or aria-disabled="true", a click that produces no selection and no error, and a stated reason such as "Sold out" in a tooltip, label or note under the row. Grey plus a clickable button is the partial state, and in practice it usually ends in the same error toast as a fail.
What should happen when a size is out of stock in every colour?
Show it, disable it before any colour is picked, and say why. If M is gone across the whole range, a shopper should see M struck through the moment the page loads, not after they choose a colour. The label can be simpler, "Sold out", since the colour is not the constraint. This is also the case where a notify-me link earns its place, because the shopper has no alternative combination to fall back on and would otherwise leave with nothing.
How do I handle variants that are available on backorder or preorder?
Do not disable them, because they can be bought. Instead give them a distinct third state: selectable, visually marked, and labelled with the delivery consequence, such as "Ships 14 March". PDP-008 is only concerned with combinations that cannot be added to the cart at all. The audit will still flag you elsewhere if the backorder date only appears after the click or at checkout, since that is the same problem of hiding a constraint until the shopper has committed.