# CTA Section v4

> Full-width grain shader CTA with centered title and button.

A shadcn/ui block from the [@uiception](https://uiception.com/?utm_source=shadcn.exchange) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/uiception/cta-section-v4).

- Page, with a live preview: https://shadcn.exchange/r/uiception/cta-section-v4
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/uiception/cta-section-v4-light-7c1dde19a2fb1cbf.png
- Registry item: https://uiception.com/r/cta-section-v4.json
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/uiception/cta-section-v4.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: `@paper-design/shaders-react`
- Registry items: `button`

## Files

### `registry/new-york/blocks/cta-section-v4/page.tsx`

```tsx
import CtaSectionV4 from "./components/cta-section-v4"

export default function Page() {
  return <CtaSectionV4 />
}
```

### `registry/new-york/blocks/cta-section-v4/components/cta-section-v4.tsx`

```tsx
import { Instrument_Serif } from "next/font/google"

import { Button } from "@/components/ui/button"

import { CtaSectionV4ShaderBg } from "./cta-section-v4-shader-bg"

import "../styles/cta-section-v4.css"

const instrumentSerif = Instrument_Serif({
  subsets: ["latin"],
  weight: ["400"],
})

export default function CtaSectionV4() {
  return (
    <section className="relative overflow-hidden bg-[#000a0f] py-4 motion-reduce:bg-[color-mix(in_oklab,var(--muted)_78%,var(--background)_22%)] md:py-6 lg:py-8">
      <CtaSectionV4ShaderBg />
      <div className="relative z-10 mx-auto max-w-6xl px-4">
        <div className="flex flex-col items-center gap-6 py-8 text-center md:py-10 lg:py-12">
          <h2 className={`${instrumentSerif.className} m-0 text-[2.625rem] leading-[1.08] tracking-[-0.03em] text-balance text-white motion-reduce:text-foreground sm:text-5xl lg:text-[3.5rem]`}>
            Your next product starts here.
          </h2>
          <div className="flex flex-row items-center gap-2"> 
          <Button variant="default" size="lg">Get Started</Button>
          <Button variant="secondary" size="lg">Sign In</Button>
          </div>
        </div>
      </div>
    </section>
  )
}
```

### `registry/new-york/blocks/cta-section-v4/components/cta-section-v4-shader-bg.tsx`

```tsx
"use client"

import { useLayoutEffect, useRef, useState } from "react"
import { GrainGradient } from "@paper-design/shaders-react"

type ShaderViewport = {
  width: number
  height: number
  dpr: number
  ready: boolean
}

const initialViewport: ShaderViewport = { width: 0, height: 0, dpr: 1, ready: false }

const CTA_V4_SHADER = {
  colors: ["#c6750c", "#beae60", "#d7cbc6"] as const,
  colorBack: "#000a0f",
  softness: 0.7,
  intensity: 0.15,
  noise: 0.5,
  shape: "sphere" as const,
  speed: 4,
  offsetY: 0.45
} as const

function adaptShader(viewport: ShaderViewport) {
  const width = Math.max(1, Math.round(viewport.width * viewport.dpr))
  const height = Math.max(1, Math.round(viewport.height * viewport.dpr))
  return { width, height }
}

export function CtaSectionV4ShaderBg() {
  const ref = useRef<HTMLDivElement>(null)
  const [viewport, setViewport] = useState<ShaderViewport>(initialViewport)

  useLayoutEffect(() => {
    const el = ref.current
    if (!el) return

    const update = () => {
      const { width, height } = el.getBoundingClientRect()
      if (width < 1 || height < 1) return
      setViewport({
        width,
        height,
        dpr: Math.min(window.devicePixelRatio || 1, 2),
        ready: true,
      })
    }

    update()
    const observer = new ResizeObserver(update)
    observer.observe(el)
    return () => observer.disconnect()
  }, [])

  const render = viewport.ready ? adaptShader(viewport) : null

  return (
    <div
      ref={ref}
      className="cta-v4-shader-frame"
      style={{ backgroundColor: CTA_V4_SHADER.colorBack }}
      aria-hidden
    >
      {render ? (
        <GrainGradient
          className="cta-v4-shader-canvas"
          width={render.width}
          height={render.height}
          colors={[...CTA_V4_SHADER.colors]}
          colorBack={CTA_V4_SHADER.colorBack}
          softness={CTA_V4_SHADER.softness}
          intensity={CTA_V4_SHADER.intensity}
          noise={CTA_V4_SHADER.noise}
          shape={CTA_V4_SHADER.shape}
          speed={CTA_V4_SHADER.speed}
          offsetY={CTA_V4_SHADER.offsetY}
          fit="cover"
        />
      ) : null}
    </div>
  )
}
```

### `registry/new-york/blocks/cta-section-v4/styles/cta-section-v4.css`

```css
.cta-v4-shader-frame {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  overflow: hidden;
}

.cta-v4-shader-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  min-width: 100%;
  min-height: 100%;
}

.cta-v4-shader-frame canvas {
  display: block;
  width: 100% !important;
  height: 100% !important;
  object-fit: cover;
}

@media (prefers-reduced-motion: reduce) {
  .cta-v4-shader-frame {
    display: none;
  }
}
```
