
The luxury look has a recipe and the recipe has been stable for ninety years: a high contrast Didone for display, a neutral grotesque for text, light weights, wide tracking on the small stuff. Vogue has set Didot since 1947. Harper's Bazaar has since Alexey Brodovitch took over art direction in 1934. Valentino and Elle use Bodoni or a close derivative, Armani uses Didot, Louis Vuitton uses Futura, Estée Lauder uses Optima.
A Didone puts a hairline next to a heavy stem, the same contrast a tailored seam makes against fabric, and a reader who has never heard the word has been trained by three generations of covers to read it as expensive.
What the category fails at online is everything under the typeface. The call, before the detail: specify the absences, not the adjectives. Luxury is defined by what is missing, and a model asked for elegance adds rather than subtracts. The Thanor library exists because the absences have to be written down as rules.
The overuse of blue and purple gradient fills on the landing page is a telltale sign of AI slop
A developer on Hacker News
The face pairing, with fallback stacks#
Two faces in total. One for display at 36px and above, one for everything else, and no third face anywhere on the site including the footer and the legal line.
- Display, classical. Didot, substituting Bodoni Moda. Ship
Didot, "Didot LT STD", "Bodoni Moda", "Hoefler Text", Garamond, serif. - Display, warmer. Canela, substituting Playfair Display. Ship
Canela, "Playfair Display", Georgia, serif. - Text, grotesque. Neue Haas Grotesk, substituting Helvetica Neue. Ship
"Neue Haas Grotesk Text", "Helvetica Neue", Helvetica, Arial, sans-serif. - Text, geometric. Avenir Next, substituting Jost. Ship
"Avenir Next", Avenir, Jost, "Century Gothic", sans-serif.
Bodoni Moda is the substitute worth knowing because it ships a variable optical size axis, opsz, running 6 to 96. Set font-optical-sizing: auto and the hairlines thicken as the size drops, which is the single failure mode of a Didone on screen: a true Didot at 18px loses its thin strokes to the pixel grid and the page looks cheap in exactly the way you were avoiding.
Canela, from Klim, is the softer alternative: flared serifs rather than true Didone hairlines, so it survives smaller sizes and reads warm rather than imperious. Pick one and delete the other. The two display entries above are alternatives, not a pairing, and the same is true of the two text entries.
One setting matters as much as the choice. Turn on oldstyle figures with font-feature-settings: "onum" 1 for any number inside running text: lining figures in a Didone stand as tall as the capitals and punch holes in a paragraph.
The scale, an augmented fourth#
Ratio 1.414 from 18px. Because 1.414 squared is 2, the ladder doubles every two steps, which is what produces the violent display to body contrast the look is built on.
| Step | Size | Line height | Tracking | Face | Used for |
|---|---|---|---|---|---|
| -1 | 13px | 18px | 0.18em | text | eyebrows, all caps labels |
| 0 | 18px | 31px | 0.005em | text | body copy |
| 1 | 25px | 36px | 0 | text | lead paragraph |
| 2 | 36px | 42px | -0.01em | display | h3, product name |
| 3 | 51px | 56px | -0.015em | display | h2 |
| 4 | 72px | 76px | -0.02em | display | h1 |
| 5 | 102px | 102px | -0.025em | display | the one hero line |
The 0.18em tracking on step -1 does more work than any other value in the table. All caps at 13px with default letterfit looks like a spreadsheet header. At 0.18em it looks like a label on something that cost money.
- Display weight:
300above51px,400below it. A Didone at700is not luxury, it is a poster. - Text weight:
400only, with500permitted on a button label. - Measure on body copy:
58chto66ch, narrower than a normal site, because narrow columns in wide space is the magazine signal.

The palette, and why it is not black#
Five values on a near black ground. Pure #000000 flattens photographic shadow into a hole, which is fatal when the product is the image.
| Role | Hex | Contrast on ground | Where it goes |
|---|---|---|---|
| Ground | #0B0B0F | the page | |
| Raised | #141419 | cards, the sticky header once scrolled | |
| Ink | #EFEBE3 | 16.52:1 | all display and body type |
| Muted | #8A857B | 5.35:1 | captions, prices, meta |
| Accent | #B08D57 | 6.36:1 | one hairline rule, one link underline |
The ink is warm off white, not #FFFFFF. On a dark ground a pure white behaves like a light source and vibrates against the background at small sizes, and the warmth is what keeps a page reading as printed rather than as a screen.
The raised value is only #141419, three steps off the ground, and that narrowness is deliberate. A card that separates from the page by a visible jump in lightness has become a component, and the look depends on surfaces reading as one continuous material with a hairline drawn on it.
The accent appears at most twice per viewport. That is a hard rule and it is the one people break: a bronze used on a button, a rule, an icon and a hover state stops being an accent and becomes a theme. No gradients at all, anywhere, including on images, including as an overlay. Where an image needs its top darkened for legible type, use a flat rgba(11, 11, 15, 0.45) and let the type carry the edge.
Motion at the right two speeds#
One entrance curve, cubic-bezier(0.16, 1, 0.3, 1), an ease out expo, which leaves almost instantly and arrives over a long tail. That asymmetry is what reads as mass.
- Section entrance:
900ms,opacity0to1,translateY32pxto0, triggered at20%of viewport, once. - Stagger between siblings:
140ms, capped at five items. - Image reveal:
1200ms,scale(1.06)toscale(1)inside an overflow clip, so the frame is still while the picture settles. - Hover:
380msoncubic-bezier(0.33, 1, 0.68, 1). Nav letter spacing moves0.14emto0.18em, and nothing translates. - Reduced motion: entrances resolve instantly, image reveals do not scale, hovers drop to
1ms.
Three more values complete the system, and each one is a place a generated page reaches for a default instead:
- Header: transparent over the hero, then
#141419with a1pxhairline in the accent at0.3alpha once the scroll passes64px. The change itself takes520ms. - Page transition: a flat
#0B0B0Fpanel wiping from the bottom,680msout oncubic-bezier(0.83, 0, 0.17, 1)and680msback. No spinner, no percentage counter, because a luxury page does not report on itself. - Cursor: native. A custom cursor is a signature of a different look, and borrowing it here is the most common way a luxury brief ends up reading as a portfolio site.
The mistake nearly everyone makes is applying the slow entrance duration to interaction. A 900ms hover is not luxurious, it is a page that does not answer. Slow is for what arrives, fast is for what responds.
Spacing, and the section order#
Spacing runs on a golden ladder, each step roughly 1.618 times the last: 12, 20, 32, 52, 84, 136, 220. Section padding is 220px top and bottom on desktop, 136px on tablet, 84px on a phone. That is far more air than a normal site and the air is the budget: emptiness is the only thing on a web page that is visibly expensive and impossible to fake.
The order, and why:
- Full bleed image, one line of type over it. The line is step 5. Nothing else in the first viewport, no button, no scroll hint.
- The statement, a single centred paragraph at step 1,
58chwide. It exists to slow the scroll before anything is sold. - The object, one large frame, one caption at step -1 in muted.
- Three details, a triptych on a
12column grid, gap32px, each with a step 2 title and two lines of body. - Provenance, the maker, the material, the place, as a two column text block. This is where a luxury page earns its price and where a generated one has nothing to say.
- The editorial pair, one image against one column, alternating side.
- The ask, alone on the ground colour, one line and one link with the accent underline. Not a filled button.
Proof goes late here, which inverts the normal rule. A luxury page that opens with logos and review counts has told you it needs the reassurance.
The verdict on building this with a model#
Hand a model the eight values and it builds a luxury page on the first pass. Ask it for luxury and it builds a dark page with a gold gradient, a serif from the safe list, a shadow on every card and a 300ms fade on nine sections. The gap between those two outcomes is entirely the specification, not the model.
What has to be written down, because it will not be inferred:
- The exact face stack, with the substitute named, and
font-optical-sizing. 0.18emon the all caps step, and the weight per size band.- The five hex values with the accent limited to twice per viewport.
- No gradients, stated as a prohibition, and a flat scrim value instead.
900msin,380mson hover, and the fact that those are different.220pxof section padding, and that it does not compress per section.
Writing that list is the job, and it is the same list at the start of every luxury project, on every brief, whatever the product turns out to be. Every prompt in the Thanor library carries it already, for whichever model you build in. If the reference you actually want is a magazine rather than a maison, the editorial specification is the tighter, more textual version of the same discipline, and the general expensive look is the lighter one. When you are ready to skip the day of decisions, browse the Thanor library.
Questions this raises
What fonts do luxury websites use?
A high contrast Didone for display and a neutral grotesque or geometric sans for text. Vogue has set its masthead in Didot since 1947, Harper's Bazaar since Alexey Brodovitch arrived in 1934, and Valentino and Elle use Bodoni or close derivatives.
What type scale suits a luxury website?
An augmented fourth, ratio 1.414, from 18px. It gives 13, 18, 25, 36, 51, 72 and 102 pixels, and because 1.414 squared is 2 the size doubles every two steps, which is what produces the dramatic display to body contrast the look depends on.
Should a luxury site be dark?
Usually, and never pure black. A near black around #0B0B0F with a warm off white ink at #EFEBE3 gives 16.52:1 of contrast and keeps a photographic shadow readable, which pure black flattens.
How slow should luxury motion be?
900ms on cubic-bezier(0.16, 1, 0.3, 1) for a section entrance, 1200ms for an image reveal, and 380ms for hover. Slow entrances read as weight. Slow hovers read as broken, which is the mistake almost everyone makes.
Why does AI generated luxury design usually fail?
Because luxury is defined by what is absent, and an unprompted model adds. A gradient, a second accent, a shadow on everything. Naming the absences as rules is the only instruction that holds.
Can I get this as a prompt instead of building the system myself?
Yes. The Thanor library is available now and its prompts carry the face pairing, the scale, the tracking, the palette, the durations and the section order as literal values.
Part of
The expensive look
What makes a site read as expensive, written as values rather than adjectives: the type scale, the spacing, the curve and its duration, and the order the sections run in.
