All work

// Case study 06 · Retail commerce · Design system

The unhappy
path

A US activewear retailer commissioned six page templates. What broke was not layout but : no size selected, every filter active, the hero image still missing. The deliverable became the component-and-state library underneath the pages. The four live artifacts below make that case.

Live Three instruments on this page, all of them running. A four-state button, a filter row you can push past breaking, and a homepage with the photography taken away. Break the first one
My role
Design system & templatesBegan on comps, ended on the system
Client
US activewear retailerOne line inside a five-brand house
Surfaces
PDP · PLP · HomepagePlus the component and state library
Shown here
RecreationsNo logos, no client photography
4
States on one button
31
Filter chips at max load(the row the grid had to survive)
5
Sister brands in one header
01 · The brief was pages

Six templates. Then the reason they broke.

The engagement started the way retail engagements usually start: design the product detail page, the listing page, the homepage, and the supporting templates around them. Comps. Redlines. Ship.

The pages were not the hard part. The hard part arrived with the first question the comps could not answer — what does the add-to-bag button say when no size is selected? Four plausible answers, no rule, and the same question waiting behind every other component. A page answers it once. A system answers it every time.

So the scope moved. I kept delivering templates, and underneath them I defined the thing the templates were actually made of: a component library, a state taxonomy for each component, and a colour system where every value carried a job description instead of a mood.

Asked for
Six page templates, desktop and mobile, ready for build.
Delivered
The templates, plus the system they exposed the need for — components, states, semantic colour, and the redlines that made the handoff arguable rather than interpretive.
Why it mattered
The line sits inside a house of five sister brands sharing one global header. A decision made once on a page gets made five more times by somebody else. A decision made once in a system does not.
02 · The unhappy path

The button that had to say four things.

This product page is long. Details, sizing, shipping, recommendations, ratings, a wall of customer photos, two more carousels. By the time a shopper has read enough to decide, the thing they decide with is a thousand pixels above them.

So the add-to-bag action detaches and follows — and once it follows, it has to carry the whole variant transaction with it, including the ways that transaction fails. The most common add-to-bag failure is not a server error. It is an unselected size. Which makes the error state the most important state on the most important control on the highest-value page on the site.

Live Add to bag without picking a size. Watch what the button has to say. Four states · one control

Sticky add-to-bag · state machine Recreation — interactive

Speed Tank
$59.95
Colour
Size
XS
Rule

Product name and prices are invented. The states, and the rule attached to each one, are the deliverable.

State 02 is the one worth arguing about. The obvious answer is a toast — leave the button saying Add to bag, throw an error when it fails. We put the instruction in the button, because a toast reports a failure and a label describes what the control will do next. Same information, no blame, nothing to dismiss, and nothing that can land under a thumb.

A disabled button is a designer refusing to say why.
Djoaniel Hernandez · System note № 05
03 · The worst case

I designed the listing page with every filter on.

Filter chips are the easiest component in retail to design and one of the easiest to ship broken, because the comp always shows two or three of them. Nobody selects three filters. People select a colour family, then a couple of sizes, then a sleeve length, then change their mind and leave the old ones on.

So the specimen I designed against was the maximum: every colour, every size, every sleeve length, all applied at once. Run the slider up and watch the second row go under — the number that matters is not the chip count, it is the height of the row, because that height is subtracted from the products. The fold is drawn in and everything past it is dimmed rather than deleted, because the products do not vanish; they stop being looked at. The first row never crosses the line. The second one pays for every chip you add.

Live Run the slider to 31. Watch the second product row sink under the fold. Measured from the DOM, not asserted

Listing page · filter row at load Recreation — interactive

One variable · chip count

Drag, step or arrow-key from an empty row to every filter in the facet set. Everything below the slider is measured from the live DOM, not asserted.

0 · clean7 · a plausible session31 · max load

Measured
New Arrivals 1,010 results Sort by ▾
Product name
$59.95
Product name
$79.95
Product name
$169.95
Product name
$69.95
Product name
$45.00
Product name
$89.95
Product name
$120.00
Product name
$34.95
Product name
$64.95
Product name
$38.00
Product name
$95.00
Product name
$52.95
Product name
$72.00
Product name
$110.00
Product name
$48.95
Fold

Result count and product names are placeholder values, exactly as they were in the spec. Row height, wrap count and fold clearance are measured live from the DOM. The page inside the frame is laid out at a real 1440px and scaled to fit — so every number in the strip is a logical pixel on a reference page, not a band pixel dressed up as one. The fold is 900px unless your own window is shorter, in which case it is yours: the short window is the failure case, and the failure case is the one worth showing.

Three rules came out of that specimen. Chips wrap rather than scroll, because a horizontal rail hides the filters a shopper most wants to remove. Clear all is pinned first and never wraps away, so the escape hatch cannot leave the screen when the mess is at its worst. And the grid holds a fixed minimum above the fold — the filters are allowed to get ugly, the products are not allowed to disappear.

04 · The grey homepage

The version nobody frames is the real deliverable.

The homepage went out twice: once dressed in campaign imagery, and once as grey boxes. The second one is the one I actually designed.

Because the person who touches this page every week is not the shopper. It is a swapping a hero, three promo tiles, a five-product rail and a give-back banner through a CMS, on a campaign cadence, with no designer in the room. If the layout only holds when the photography is beautiful, it does not hold — it degrades a little every week until somebody calls it a redesign.

Live Take the photography away. See whether the layout still holds. The grey version is the deliverable

Homepage template Recreation — toggle the fill

Binary · asset state Campaign imagery loaded On is the version that gets screenshotted. Off is the version the merchandising team actually receives, and the one the layout has to survive.
Welcome
Summer
Shop summer
Short
and sweet
Shop bottoms
Up
lifted
Shop bras
Top
it all
Shop tops
Summer's easiest picks
The
accessories shop
Shop accessories
Give back
Full width × 330px banner
Slot
What to watch

The grey state is the deliverable, not the failure state. Toggle it off and you have what the merchandising team actually receives on a Friday — every slot still declaring a ratio, a safe area and a character budget. The page has to hold on a week when the assets are late, because most weeks they are.

Each slot is a contract, not a canvas. A hero, three promo tiles, a five-product rail, a give-back banner — the shapes are fixed before anyone chooses an image. A merchandiser cannot break this page by picking a weak photograph, only by leaving one out.

Nothing here is two designs. The fields re-dress in place rather than swapping pictures, and the readout changes with them — because the claim is that one layout survives both conditions, not that there is a pretty version and a plain one.

Campaign photography belongs to the client and is not reproduced. In the populated state the palette itself stands in for the imagery — which is, conveniently, the point being made.

05 · Every colour has a job

A palette is not a mood board. It is an assignment sheet.

The colour documentation does not describe how the colours feel. Each value carries the job it is allowed to do, so that a developer, a merchandiser or the next designer can resolve a question without a taste debate. Two of the entries are arguments rather than choices.

Hex values on this page are shifted from the source palette. The assignments are the subject; the exact values are the client's.

Peacock
#7FAEAB
Attention, highlighted information, and calls to action. The identity colour — so it is rationed, not spread.
CTA
Meadow
#BDC690
Second in the palette. Pairs with peacock without competing with it.
Secondary
Tidal
#C2E0E8
Pop accent. Large fields and category moments, never type.
Accent
Peony
#FAC5C0
Accent that coordinates with the neutrals and provides a warm pop where fitting.
Accent
Dandelion
#DFB587
Complementary to peacock. The warm counterweight in a cool palette.
Support
Pink Sands
#F9DBD7
Neutral, not accent. Sits under content as a background tint.
Ground
Stone
#E9E6E1
The branded white. Backgrounds and supporting elements. Pure #FFFFFF was reserved for surfaces that need to read as raised against it — so "white" became a decision instead of a default.
Ground
Dusk
#3C4B54
The text black, and the only entry justified on legibility rather than taste. Softer and warmer than #000 to suit the brand, but held dark enough to clear contrast at 14px body copy. Every text colour is a contrast decision first and a brand decision second.
Text
Error / Success
System pair
Deliberately outside the brand palette. If failure and confirmation are drawn from brand colours, the brand starts meaning "something went wrong."
Semantic

The palette also shipped with PMS numbers, because this brand exists in stores. A colour that only resolves on a screen is half a colour system — the same peacock has to survive a printed tag, a window graphic and an OLED phone, and the one that governs all three is the one worth writing down.

And every field has seven.

The same discipline, one level down. A text input is not a rectangle; it is seven rectangles a developer has to be told about before they guess.

Default
Field label
 
With icon
Password
With button
Email address
Helper textSubmit
Hover / focus
Field label
Text entered
Error
Phone
(XXX) XXX-XX
Error message
Success
Field label
Text entered
Looks good
Empty-field highlight
Field label
Text entered
Inactive
Field label
 

Buttons carried the same treatment — four hierarchies, desktop and mobile, redlined to a 47px target.

06 · Whose work this is

The edges of this one.

The client
Named nowhere on this page, by choice. No logos, no wordmarks, no store photography, no campaign imagery.
The screens
Every artifact above is a recreation I coded for this site, set in my own typefaces, from my own working files. Nothing here is a lifted screenshot.
The numbers
None, and none estimated. I do not hold post-launch analytics for this engagement, and an invented number is worth less than the blank.
The grey type
Some labels inside the recreations sit under 4.5:1. That is the source, reproduced. The site's own voice around them is not allowed to. Evidence gets quoted, not improved.
What is mine
The state taxonomy, the component and colour system, the template specs and the redlines. The photography, the merchandising and the brand identity were not.

The reason this project sits in a portfolio next to a banking data standard and a chatbot architecture is that it is the same job. Find the moment the system is asked a question it has no answer for, and answer it once, in a place where the answer survives the next person. In banking that moment was a missing valuation. Here it was a shopper who had not picked a size.