Skip to main content

September 2, 2026

Centra colour siblings dedupe by uri

Colour swatches showed Black twice on a fashion PDP. Dedupe siblings on the URL you paint, then hide the picker when only one colour exists.

Sander Korf4 min read

On a Centra fashion product detail page, colour is not a dropdown on one SKU. Each colour is a sibling product with its own id. Swatches let a shopper jump from Black to Navy without leaving the page. Centra sends relatedDisplayItems for the colour-sibling relation as a list. Import quirks and frontend auto-linking often put the current product in that list twice. I built swatches as [current, ...siblings], sorted by uri, and watched Black show up twice on a Next.js PDP that was otherwise fine. Shoppers tapped the same swatch row and wondered why nothing changed.

Two rows shared uri /linen-tee-black. One row was the live product id. The other was an import shadow with the same uri and an empty slug. Sorting by uri kept them side by side, so the duplicate looked like a deliberate design choice instead of a PIM accident.

Dedupe on the key you paint

Filtering the current id out of the sibling array was my first pass. Black still appeared twice because the shadow row used a different id. React key={uri} does not merge siblings. You get a console warning and two buttons in the DOM.

The collapse belongs next to the data, on the display key the swatch paints. The helper below keeps the first occurrence for stable order, then sorts by uri. Empty uri falls back to id so import duplicates without a slug still collapse.

function dedupeByUri<T extends { uri: string; id: string }>(items: T[]): T[] {
	const seen = new Set<string>();
	return items.filter((item) => {
		const key = item.uri || item.id;
		if (seen.has(key)) return false;
		seen.add(key);
		return true;
	});
}
 
const colourOptions = dedupeByUri([currentColour, ...siblingColours]).toSorted((a, b) =>
	(a.uri || a.id).localeCompare(b.uri || b.id)
);

Fallback to id when uri is empty has a limit. Two import shadows with empty uris and different ids still render as two swatches. The collapse key has to be the key you paint. If Centra never gives you a uri, you are collapsing on identity, and identity was the thing that duplicated.

Merchandising sometimes fixes the shadow in Centra later. Until then the PDP should not punish shoppers with twin Black buttons. Dedupe at render time is cheaper than arguing with import history during a launch week.

Hide the picker when there is no choice

The colour picker only renders when options.length >= 2, on the PDP and on Quick Add. One colour means no fake choice UI. Legend stays sr-only because legend is out of the fieldset flex flow in Tailwind. The visible "Colour" label is a separate div with aria-hidden so sighted layout and screen reader structure do not fight.

Quick Add size chips stopped using a one-off mail-icon button for unavailable sizes. They now go through the same ProductSizeOption component as the PDP: available vs unavailable styling, same hit target, same notify path later. Mobile PDP grid uses display: contents on the wrapper so order can be media, then info, then accordion without the info column height shoving the accordion down. Desktop stacks media and accordion in the left column while info stays an independent column.

In the demo below, turn Dedupe off and Black appears twice for the same uri. Turn Dedupe on and the swatch row matches what Centra meant to sell. Toggle before you copy the helper into your own mapper so you see the shadow row, not just a screenshot of a clean PDP.

Colour swatches after dedupeByUri

Centra relatedDisplayItems often repeats the current product. Toggle dedupe to see duplicate Black swatches from the same uri.

Colour

4 swatches rendered

Duplicate uri collapsed. Empty uri falls back to id so import duplicates still dedupe.

PIM relations arrive as lists. Treat them like lists and dedupe on the key you paint, not on the id Centra uses internally for import ghosts.


Happy coding! Sander