Feature Split

Two-column feature block with checklist and a visual that slides in.

Collaboration

Review work where it lives

Leave comments directly on the page, mention teammates and resolve feedback without a single screenshot.

  • Pin comments to any element
  • Threaded replies and mentions
  • Resolve with one click
Maya: Can we make this heading bolder?

Installation

  1. Install the dependencies

    $ npm i motion lucide-react
  2. Copy the source into components/ui/feature-split.tsx

    components/ui/feature-split.tsx
    "use client";
    
    import { motion } from "motion/react";
    import { Check } from "lucide-react";
    import type { ReactNode } from "react";
    
    type FeatureSplitProps = {
      eyebrow: string;
      title: string;
      text: string;
      points: string[];
      visual: ReactNode;
      reverse?: boolean;
    };
    
    export function FeatureSplit({ eyebrow, title, text, points, visual, reverse = false }: FeatureSplitProps) {
      return (
        <div className="grid items-center gap-10 md:grid-cols-2 md:gap-16">
          <motion.div
            initial={{ opacity: 0, x: reverse ? 30 : -30 }}
            whileInView={{ opacity: 1, x: 0 }}
            viewport={{ once: true, amount: 0.4 }}
            transition={{ duration: 0.7, ease: [0.22, 1, 0.36, 1] }}
            className={reverse ? "md:order-2" : ""}
          >
            <p className="text-sm font-medium text-blue-600">{eyebrow}</p>
            <h3 className="mt-2 text-3xl font-semibold tracking-tight text-zinc-900">{title}</h3>
            <p className="mt-4 leading-relaxed text-zinc-500">{text}</p>
            <ul className="mt-6 space-y-3">
              {points.map((p, i) => (
                <motion.li
                  key={p}
                  initial={{ opacity: 0, y: 8 }}
                  whileInView={{ opacity: 1, y: 0 }}
                  viewport={{ once: true }}
                  transition={{ delay: 0.2 + i * 0.08 }}
                  className="flex items-center gap-3 text-sm text-zinc-700"
                >
                  <span className="grid size-5 shrink-0 place-items-center rounded-full bg-blue-600 text-white">
                    <Check className="size-3" />
                  </span>
                  {p}
                </motion.li>
              ))}
            </ul>
          </motion.div>
          <motion.div
            initial={{ opacity: 0, scale: 0.95 }}
            whileInView={{ opacity: 1, scale: 1 }}
            viewport={{ once: true, amount: 0.4 }}
            transition={{ duration: 0.8, ease: [0.22, 1, 0.36, 1] }}
          >
            {visual}
          </motion.div>
        </div>
      );
    }
    
    export default function Demo() {
      return (
        <div className="w-full max-w-5xl">
          <FeatureSplit
            eyebrow="Collaboration"
            title="Review work where it lives"
            text="Leave comments directly on the page, mention teammates and resolve feedback without a single screenshot."
            points={["Pin comments to any element", "Threaded replies and mentions", "Resolve with one click"]}
            visual={
              <div className="relative aspect-[4/3] overflow-hidden rounded-3xl bg-gradient-to-br from-blue-50 to-violet-100 p-8">
                <div className="h-full rounded-2xl bg-white p-5 shadow-xl">
                  <div className="h-3 w-24 rounded bg-zinc-200" />
                  <div className="mt-4 h-24 rounded-xl bg-zinc-100" />
                  <div className="mt-3 h-3 w-3/4 rounded bg-zinc-100" />
                </div>
                <motion.div
                  animate={{ y: [0, -6, 0] }}
                  transition={{ duration: 3, repeat: Infinity }}
                  className="absolute right-6 top-16 max-w-[180px] rounded-xl bg-zinc-900 p-3 text-xs text-white shadow-lg"
                >
                  <span className="font-medium">Maya:</span> Can we make this heading bolder?
                </motion.div>
              </div>
            }
          />
        </div>
      );
    }
    
  3. Use it

    The file exports the component and a Demo showing typical usage. Import whichever you need.