# Hero 13

> Asymmetric editorial hero with serif brand title, supporting copy, and two overlapping media frames.

A shadcn/ui block from the [@flx](https://ui.flexnative.com) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/flx/hero-13).

- Page, with a live preview: https://shadcn.exchange/r/flx/hero-13
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/flx/hero-13-light-d5e355d84d105a92.png
- Registry item: https://ui.flexnative.com/r/hero-13.json
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://ui.flexnative.com/r/hero-13.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- npm packages: `motion`, `react-wrap-balancer`

## Files

### `registry/blocks/hero/hero-13/hero-13.tsx`

```tsx
'use client'

import * as React from 'react'
import { motion, useReducedMotion, type Variants } from 'motion/react'
import Balancer from 'react-wrap-balancer'

import { cn } from '@/lib/utils'

export interface Hero13Props {
  title: string
  titleLine2?: string
  meta?: string
  eyebrow?: string
  description?: string
  primaryImage: {
    src: string
    alt: string
  }
  secondaryImage: {
    src: string
    alt: string
  }
  variant?: 'standard' | 'compact'
  animation?: 'none' | 'subtle'
}

const variantStyles = {
  standard: {
    section: 'py-20 sm:py-28',
    columns: 'gap-12 lg:gap-16',
    left: 'gap-10 lg:gap-16',
    right: 'gap-8 lg:gap-12',
    title: 'text-3xl sm:text-4xl md:text-5xl',
    meta: 'text-xs tracking-[0.22em]',
    eyebrow: 'text-xs tracking-[0.18em]',
    description: 'max-w-sm text-sm sm:text-base leading-relaxed',
    primaryMedia: 'aspect-4/3',
    secondaryMedia: 'aspect-3/4 max-w-md lg:max-w-none',
  },
  compact: {
    section: 'py-14 sm:py-20',
    columns: 'gap-10 lg:gap-12',
    left: 'gap-8 lg:gap-12',
    right: 'gap-6 lg:gap-10',
    title: 'text-2xl sm:text-3xl md:text-4xl',
    meta: 'text-[11px] tracking-[0.2em]',
    eyebrow: 'text-[11px] tracking-[0.16em]',
    description: 'max-w-sm text-sm leading-relaxed',
    primaryMedia: 'aspect-4/3',
    secondaryMedia: 'aspect-3/4 max-w-sm lg:max-w-none',
  },
} as const

const container: Variants = {
  hidden: {},
  visible: { transition: { staggerChildren: 0.1, delayChildren: 0.05 } },
}

const item: Variants = {
  hidden: { opacity: 0, y: 12, filter: 'blur(6px)' },
  visible: {
    opacity: 1,
    y: 0,
    filter: 'blur(0px)',
    transition: { duration: 0.5, ease: [0.22, 1, 0.36, 1] },
  },
}

const mediaItem: Variants = {
  hidden: { opacity: 0, y: 24, filter: 'blur(8px)' },
  visible: {
    opacity: 1,
    y: 0,
    filter: 'blur(0px)',
    transition: { duration: 0.6, ease: [0.22, 1, 0.36, 1] },
  },
}

function Reveal({
  active,
  variants,
  className,
  children,
}: Readonly<{
  active: boolean
  variants?: Variants
  className?: string
  children: React.ReactNode
}>) {
  if (!active) return <div className={className}>{children}</div>

  return (
    <motion.div variants={variants ?? item} className={className}>
      {children}
    </motion.div>
  )
}

export function Hero13({
  title,
  titleLine2,
  meta,
  eyebrow,
  description,
  primaryImage,
  secondaryImage,
  variant = 'standard',
  animation = 'none',
}: Readonly<Hero13Props>) {
  const reduce = useReducedMotion()
  const animate = animation === 'subtle' && !reduce
  const vs = variantStyles[variant]

  const primaryMediaElement = primaryImage?.src && (
    <div className="relative max-w-lg">
      <div
        aria-hidden
        className="border-foreground/15 pointer-events-none absolute -right-3 -bottom-3 size-16 border sm:size-20"
      />
      <div
        className={cn(
          'relative overflow-hidden rounded-md outline outline-black/10 dark:outline-white/10',
          vs.primaryMedia,
        )}
      >
        <img
          src={primaryImage.src}
          alt={primaryImage.alt}
          className="size-full object-cover"
        />
      </div>
    </div>
  )

  const titleElement = title && (
    <h1
      className={cn(
        'text-foreground font-serif font-normal tracking-tight text-balance uppercase',
        vs.title,
      )}
    >
      <Balancer>
        {title}
        {titleLine2 ? (
          <>
            <br />
            {titleLine2}
          </>
        ) : null}
      </Balancer>
    </h1>
  )

  const metaElement = meta && (
    <p className={cn('text-muted-foreground font-medium uppercase', vs.meta)}>
      {meta}
    </p>
  )

  const eyebrowElement = eyebrow && (
    <p className={cn('text-foreground font-semibold uppercase', vs.eyebrow)}>
      {eyebrow}
    </p>
  )

  const descriptionElement = description && (
    <p className={cn('text-muted-foreground text-pretty', vs.description)}>
      <Balancer>{description}</Balancer>
    </p>
  )

  const secondaryMediaElement = secondaryImage?.src && (
    <div className="relative ml-auto w-full max-w-md lg:mr-4">
      <div
        aria-hidden
        className="border-foreground/15 pointer-events-none absolute top-[18%] -left-7 size-24 rounded-full border sm:-left-9 sm:size-28"
      />
      <div
        className={cn(
          'relative overflow-hidden rounded-md outline outline-black/10 dark:outline-white/10',
          vs.secondaryMedia,
        )}
      >
        <img
          src={secondaryImage.src}
          alt={secondaryImage.alt}
          className="size-full object-cover"
        />
      </div>
    </div>
  )

  return (
    <section className="bg-background relative isolate w-full overflow-hidden">
      <motion.div
        className={cn(
          'relative z-10 mx-auto grid max-w-6xl px-6 lg:grid-cols-2 lg:items-start',
          vs.section,
          vs.columns,
        )}
        variants={animate ? container : undefined}
        initial={animate ? 'hidden' : false}
        animate={animate ? 'visible' : undefined}
      >
        <div className={cn('order-2 flex flex-col lg:order-1', vs.left)}>
          <Reveal active={animate} variants={mediaItem}>
            {primaryMediaElement}
          </Reveal>
          <Reveal active={animate} className="flex flex-col gap-3 lg:max-w-sm">
            {eyebrowElement}
            {descriptionElement}
          </Reveal>
        </div>

        <div className={cn('order-1 flex flex-col lg:order-2', vs.right)}>
          <Reveal active={animate} className="flex flex-col gap-3 lg:pt-2">
            {titleElement}
            {metaElement}
          </Reveal>
          <Reveal active={animate} variants={mediaItem}>
            {secondaryMediaElement}
          </Reveal>
        </div>
      </motion.div>
    </section>
  )
}
```

### `registry/blocks/hero/hero-13/hero-13-example.tsx`

```tsx
import { Hero13, type Hero13Props } from './hero-13'

export const values = {
  title: 'Haven',
  titleLine2: 'Studio.',
  meta: 'Portland — 2019',
  eyebrow: 'Craft & quiet',
  description:
    'We shape residential and commercial interiors into calm rooms where light, material, and proportion do the quiet work.',
  variant: 'standard',
  animation: 'subtle',
  primaryImage: {
    src: 'https://images.unsplash.com/photo-1578301978018-3005759f48f7?q=80&w=1144&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
    alt: 'Soft abstract wash of muted color',
  },
  secondaryImage: {
    src: 'https://images.unsplash.com/photo-1746467364902-ab40952e33fe?q=80&w=1131&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
    alt: 'Portrait study in soft light',
  },
} satisfies Hero13Props

export function Hero13Example() {
  return <Hero13 {...values} />
}
```
