# Contact 20

> Contact 20 variant for block.

A shadcn/ui block from the [@uiable](https://uiable.com) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/uiable/block-contact-20).

- Page, with a live preview: https://shadcn.exchange/r/uiable/block-contact-20
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/uiable/block-contact-20-light-5477f6c330d390f6.png
- Registry item: https://uiable.com/r/block-contact-20.json
- Categories: contact
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://uiable.com/r/block-contact-20.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: `framer-motion`

## Files

### `src/components/uiable/blocks/contact/contact-20/contact-20.tsx`

```tsx
"use client"

import type { ReactNode } from "react"

// third-party
import { motion, useReducedMotion, type Variants } from "framer-motion"

interface AnimatedTextProps {
  text: string
  wordClassName?: string
  delay?: number
}

interface FadeInProps {
  children: ReactNode
  direction?: "left" | "right"
  delay?: number
  className?: string
}

const EASE_ENTRANCE = [0.21, 0.47, 0.32, 0.98] as const
const REVEAL_MARGIN = "0px 0px -12% 0px" as const

function AnimatedText({ text, wordClassName, delay = 0 }: AnimatedTextProps) {
  const reduce = useReducedMotion()
  const words = text.split(" ")
  const stagger = reduce ? 0.04 : 0.08

  const containerVariants: Variants = {
    hidden: {},
    show: {
      transition: {
        staggerChildren: stagger,
        delayChildren: reduce ? 0 : delay,
      },
    },
  }

  const wordVariants: Variants = {
    hidden: reduce
      ? { opacity: 0 }
      : { opacity: 0, y: "0.6em", filter: "blur(6px)" },
    show: {
      opacity: 1,
      y: "0em",
      filter: "blur(0px)",
      transition: { duration: reduce ? 0.3 : 0.55, ease: EASE_ENTRANCE },
    },
  }

  return (
    <span>
      <span className="sr-only">{text}</span>
      <motion.span
        aria-hidden="true"
        initial="hidden"
        whileInView="show"
        viewport={{ once: true, margin: REVEAL_MARGIN }}
        variants={containerVariants}
      >
        {words.map((word, index) => (
          <motion.span
            key={`${word}-${index}`}
            variants={wordVariants}
            className={`inline-block whitespace-pre will-change-transform ${wordClassName ?? ""}`}
          >
            {word}
            {index < words.length - 1 ? " " : ""}
          </motion.span>
        ))}
      </motion.span>
    </span>
  )
}

function FadeIn({
  children,
  direction = "left",
  delay = 0,
  className,
}: FadeInProps) {
  const reduce = useReducedMotion()
  const offset = direction === "left" ? -24 : 24

  return (
    <motion.div
      initial={{ opacity: 0, x: reduce ? 0 : offset }}
      whileInView={{ opacity: 1, x: 0 }}
      viewport={{ once: true, margin: REVEAL_MARGIN }}
      transition={{
        duration: reduce ? 0.3 : 0.6,
        delay: reduce ? 0 : delay,
        ease: EASE_ENTRANCE,
      }}
      className={className}
    >
      {children}
    </motion.div>
  )
}

//  ------------------------------ | CONTACT 20 | ------------------------------  //

export default function Contact20() {
  return (
    <header className="mx-auto flex max-w-7xl flex-col items-center gap-10 overflow-hidden px-4 pt-8 pb-12 text-center md:flex-row md:gap-12 md:px-8 md:pb-16 md:text-left lg:px-10">
      <div className="flex-1 space-y-5 md:space-y-6">
        <h1 className="text-[2.25rem] leading-[1.15] font-extrabold tracking-tight text-foreground sm:text-5xl lg:text-6xl">
          <AnimatedText text="Let's start a" />{" "}
          <AnimatedText
            text="conversation."
            wordClassName="text-primary"
            delay={0.25}
          />
        </h1>
        <FadeIn delay={0.4}>
          <p className="max-w-2xl text-base text-muted-foreground sm:text-lg">
            Whether you have a question about our courses, pricing, or need
            technical support, our team is ready to help you achieve your
            learning goals.
          </p>
        </FadeIn>
      </div>
      <FadeIn direction="left" delay={0.3} className="relative w-full flex-1">
        <div className="pointer-events-none absolute inset-0 translate-y-10 rounded-full bg-primary/20 blur-3xl" />
        <div className="relative z-10 h-75 w-full">
          <img
            src="https://cdn.uiable.com/block/img-4.jpg"
            alt="A bright, modern workspace with a laptop and a potted plant"
            sizes="(max-width: 768px) 100vw, 50vw"
            className="rounded-2xl object-cover shadow-lg"
          />
        </div>
      </FadeIn>
    </header>
  )
}
```
