KKUONIDesign System

Components

Trip Card

Stable

Trip cards are the primary commercial listing unit for bookable holidays. They combine imagery, promotional messaging, hotel details, star ratings and pricing into a single scannable surface.

Purpose

Trip cards are commercial cards — they present a specific bookable product rather than a browsable category. Where a destination card says "here is Kenya", a trip card says "here is a specific 7-night Maldives holiday at the Soneva Fushi for £4,250 per person, full board."

Usage

Reach for TripCard whenever you're presenting a specific bookable offer rather than a browsable category — use Destination Card instead when the goal is to invite exploration of a place, not a purchase decision. TripCard always shows a price, optionally a savings/promotional message, and star rating, board basis, nights and departure context — more information-dense than a destination card by design, since a traveller comparing offers needs all of that at a glance.

TripCard appears on the homepage offer sections ("Sale away"), the /offers/ listing page, and destination sub-pages filtered to show holidays.

Anatomy

  1. 1
    Image + promotion badgeAspect 3:2 image. A teal overlay badge (e.g. "Save up to 30%") sits absolute top-left with no border-radius on the top-left corner.
  2. 2
    Savings headlineOptional red bold text ("Save up to £1,275 per person"). Appears at the top of the body when a saving exists.
  3. 3
    Destination tag10px uppercase letter-spaced text — region + destinations joined by "·". e.g. "INDIAN OCEAN · MALDIVES".
  4. 4
    Star ratingGold filled ★ characters for hotel star rating (3–5 stars, 0.5 increments). Does not represent customer reviews.
  5. 5
    Hotel name16px bold. The specific property name — the primary identifier of the product being offered.
  6. 6
    Price block"From" in grey 11px · price numeral in 22px bold dark · "per person" (or family/couple) in grey 11px.
  7. 7
    Context row12px grey text: nights · board basis · departure date. Compact secondary detail row below the price.
  8. 8
    Local taxes noteOptional italic grey note about locally payable taxes or fees. Prefixed with *.

Variants

Standard — per person pricing

The most common variant. Used for all solo, couple and adult-group holidays.


With savings

Show a promotionBadge on the image and a savingsHeadline in the body when the holiday carries a promotional offer. Both are optional and independent — a badge can appear without a headline and vice versa.


Family pricing

When priceBasis='family' the price suffix changes to "per family of N". Use this for holidays priced per booking rather than per person.


Multi-destination

When a holiday covers multiple destinations, pass multiple strings in the destinations array. They are joined with ", " and displayed in the destination tag.

States

Default

Card rests with a light shadow (0 2px 8px rgba(0,0,0,0.08)), subtle border #E5E5E5, and white background.

Hover

Shadow deepens to 0 6px 20px rgba(0,0,0,0.14). The card does not scale (unlike destination cards) — trip cards carry more data and a scale transform can cause content reflow. Only the shadow changes.

Grid layout

Trip cards work in 2–4 column grids. Because they carry more information than destination cards, avoid 4 columns on screen widths below 1280px — the content becomes too compressed.

BreakpointColumnsGap
Mobile (below 768px)116px
Tablet (768–1024px)216px
Desktop (1024–1280px)2–324px
Wide (above 1280px)3–424px

Best practices

Do

Always show the price with its basis context: 'per person', 'per family of 4', 'per couple'.

Price + basis together give the traveller a complete financial picture.

Don't

Display a price number without the priceBasis prop set.

A price without basis is ambiguous — is it total or per person?

Do

Use promotionBadge only when there is a genuine, specific offer (saving amount, free nights, free upgrade).

The teal badge draws the eye first — it should justify the click.

Don't

Apply promotionBadge to every card in a grid as a default pattern.

Generic 'Special' badges on every card train users to ignore them.

Do

Let the entire card surface be the clickable area pointing to the holiday detail page.

The whole card is the link — no separate button is needed.

Don't

Add a 'Book now' or 'View deal' button inside the card body.

A CTA button inside the card competes with the card link and looks cluttered.

Do

Use starRating for official hotel classification (3–5 stars). Use a separate Rating component for Feefo customer reviews.

Star rating reflects the hotel classification, not customer reviews.

Don't

Show starRating and a Feefo review score simultaneously inside a trip card.

Mixing hotel stars and review scores in the same display confuses their meaning.

Props

PropTypeDefaultDescription
href*stringURL of the holiday detail page. The entire card is a link pointing here.
priceFrom*numberThe lead "from" price in the local currency (no currency symbol — set via currency prop).
image{ src: string; alt: string }Hero image for the card. Rendered at 3:2 aspect ratio, object-cover.
savingsHeadlinestringPromotional saving text shown in red bold at the top of the body — e.g. "Save up to £1,275 per person".
destinationsstring[][]Array of destination names. Single: ["Maldives"]. Multi: ["Sri Lanka", "Maldives"]. Joined with ", " in the destination tag.
regionstringGeographic region label — e.g. "Indian Ocean", "East Africa". Shown before the destination in the tag row.
starRatingnumberHotel classification (3, 3.5, 4, 4.5 or 5). Renders filled gold star characters. Not Feefo customer rating.
hotelNamestringName of the specific hotel or resort. Primary identifier of the bookable product.
currencystring'£'Currency symbol shown before the price numeral.
priceBasis'person' | 'family' | 'couple''person'Who the price applies to. Affects the suffix text after the price numeral.
familySizenumber4Used when priceBasis="family" to render "per family of N".
boardBasisstringMeal plan — e.g. "Full board", "B&B", "All-inclusive". Shown in the context row below the price.
nightsnumberDuration in nights. Shown in the context row.
departureDatestringHuman-readable departure context — e.g. "Oct 2026", "from London Heathrow". Shown in the context row.
localTaxesNotestringDisclosure note for locally payable taxes or fees — e.g. "€53pp payable locally". Shown below the context row in italic grey.
promotionBadgestringText for the teal overlay badge on the image — e.g. "Save up to 30%", "Free half board".
classNamestringAdditional Tailwind classes on the root element.

Accessibility

The entire card is an <a> tag. Screen readers read the content in DOM order:

  1. Image alt text (describes the scene)
  2. Savings headline (if present)
  3. Destination tag
  4. Hotel name
  5. Price information

The price is rendered as plain text — "From £4,250 per person" — so it reads correctly without any special formatting.

Promotion badge

The teal image overlay badge is part of the link and is read by screen readers. If savingsHeadline repeats the same information, the user hears the saving twice. When using both, make them complementary rather than identical:

{/* ✅ Complementary — badge gives category, headline gives amount */}
<TripCard
  promotionBadge="Half board free"
  savingsHeadline="Save up to £830 per person"
  ...
/>

{/* ❌ Repetitive — both say the same thing */}
<TripCard
  promotionBadge="Save up to £830"
  savingsHeadline="Save up to £830 per person"
  ...
/>

Image alt text

Describe the visual scene, not the hotel name (the hotel name is in hotelName).

{/* ✅ */}
image={{ alt: 'Overwater villa with turquoise lagoon at sunrise' }}

{/* ❌ */}
image={{ alt: 'Soneva Fushi Maldives' }}

Star rating

The StarRow sub-component has aria-label="X out of 5 stars" on its container and all star characters are aria-hidden. Screen readers hear "5 out of 5 stars" without the repeated ★★★★★ characters.

  • Destination Card — use instead when inviting exploration of a place rather than presenting a specific priced offer.
  • Resort Card — the equivalent commercial card for ski and walking resorts, organised around resort stats instead of a single hotel offer.
  • Price Display — the pricing pattern TripCard's price block follows; useful when building a custom surface that needs the same price formatting.