Skip to Content

Age Gate

A site-wide, reusable age verification gate: every Brown-Forman liquor brand needs one, so it’s designed as a shared package rather than a one-off for Herradura.

Reference: how herradura.com actually does it

We inspected the live site directly rather than guessing at requirements: filled the form, cleared cookies and retried, watched every network request fire, and fetched raw HTML with a plain browser user agent. Confirmed:

  • Full date-of-birth entry (separate MM / DD / YYYY fields) plus a country selector covering essentially every country: legal drinking age varies by market, and the gate is genuinely global. Same mechanism we found on jackdaniels.com (see below), so this is a real Brown-Forman-wide pattern, not one brand’s choice.
  • No dedicated “verify age” API call. Cleared cookies, refilled the form, watched every request fire on submit: nothing posts the DOB anywhere. Validated entirely client-side against the selected country’s legal age.
  • Legal/privacy links point to a shared corporate domain: legal.brown-forman.com/terms-of-use, /privacy-policy; centralized across brands. We link to it, we don’t write our own.
  • Crawler-safe by construction. The underlying content is present in the raw HTML response regardless of gate state: the gate is a client-side visual overlay, not server-side content withholding. Confirmed on both brand sites.

Don’t reach for middleware.ts-based redirects or route blocking to enforce the gate. That requires detecting bot user-agents to exempt them from the block, which is spoofable and edges toward cloaking, something Google explicitly discourages. The overlay approach below sidesteps the problem instead of managing it.

Where Herradura’s implementation actually differs from Jack Daniel’s

Two real Brown-Forman brand sites, same underlying requirement, genuinely different execution, worth designing our package to support both rather than assuming one is “the” pattern:

herradura.comjackdaniels.com
LayoutSplit-screen: lifestyle photography left, form rightFull-screen centered, logo-only background
FieldsLabeled (“Birthdate*”, “Location*”)Placeholder-only, no visible labels
PersistenceOpt-in: a “Remember me” checkbox controls whether the cookie survives the sessionAutomatic: no visible opt-in, persists regardless
ConsentExplicit checkbox tied to terms/privacyDisclaimer text only, no checkbox
Headline copy“Please verify your age”“Jack’s birthdate has always been a mystery. Hopefully yours isn’t.” (brand voice)

The “Remember me” difference matters functionally, not just visually: if it’s genuinely opt-in and unchecked by default, a visitor who declines it gets re-gated on every new session even after passing once. Confirm this is the intended behavior (not a bug in the WordPress plugin) before treating it as a requirement: a P1-native rebuild should decide deliberately whether persistence is automatic or opt-in, not inherit an assumption from either site.

Approach

A root-layout client component, published as @brown-forman/p1-age-gate, built to support both patterns above via config rather than picking one:

// app/layout.tsx: the one file that wraps every route automatically import { AgeGate } from "@brown-forman/p1-age-gate"; export default function RootLayout({ children }: { children: React.ReactNode }) { return ( <html lang="en"> <body> <AgeGate brand={{ name: "Herradura", logo: "/images/herradura-seal.svg" }} layout="split" // "split" (Herradura) | "centered" (Jack Daniel's) persistence="opt-in" // "opt-in" (Herradura) | "automatic" (Jack Daniel's) legalLinks={{ terms: "https://legal.brown-forman.com/terms-of-use/english", privacy: "https://legal.brown-forman.com/privacy-policy/english", }} /> {children} </body> </html> ); }
  • Renders full-screen when unverified, null once a cookie confirms verification.
  • No page-level or middleware wiring needed; every route gets it automatically.
  • Content underneath is always server-rendered and present in the HTML: the overlay is a presentation-layer block, not a data-layer one.

Config shape

interface AgeGateConfig { brand: { name: string; logo?: string }; layout?: "split" | "centered"; // default: "split" persistence?: "opt-in" | "automatic"; // default: "opt-in" legalLinks: { terms: string; privacy: string }; /** ISO 3166-1 alpha-2 → minimum age. Falls back to `default` for unlisted countries. */ minimumAgeByCountry: Record<string, number> & { default: number }; /** Countries offered in the selector; defaults to a full list like the reference sites. */ countries?: string[]; cookieName?: string; // default: "age_verified" cookieMaxAgeDays?: number; // default: 365 }

minimumAgeByCountry exists because legal drinking age isn’t universally 21: confirmed directly on herradura.com’s own Visit Us page, which states “Guests must be 18 years of age or older” for the Mexico-based distillery tour (Mexico’s legal drinking age is 18). A brand operating only in the US can pass just { default: 21 }; a brand with real multi-market traffic (like Herradura, given the tour requirement) needs the full map.

Open questions for this page now live on Outstanding Questions, tracked centrally across all pages rather than repeated per page.

Last updated on