# Contact 18

> Contact 18 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-18).

- Page, with a live preview: https://shadcn.exchange/r/uiable/block-contact-18
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/uiable/block-contact-18-light-60573ef523b2a59b.png
- Registry item: https://uiable.com/r/block-contact-18.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-18.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`, `ogl`
- Registry items: `@uiable/button`

## Files

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

```tsx
"use client"

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

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

// project-imports
import { SeoAnimatedParticles as AnimatedParticles } from "@/components/animation/SeoAnimatedParticles"
import { SeoBackgroundEffects as BackgroundEffects } from "@/components/animation/SeoBackgroundEffects"

//  ------------------------------ | CONTACT 18 | ------------------------------  //

export default function Contact18() {
  const containerVariants: Variants = {
    hidden: { opacity: 0 },
    show: {
      opacity: 1,
      transition: { staggerChildren: 0.2 },
    },
  }

  const itemVariants: Variants = {
    hidden: { opacity: 0, y: 40, filter: "blur(10px)" },
    show: {
      opacity: 1,
      y: 0,
      filter: "blur(0px)",
      transition: { duration: 0.8, ease: [0.22, 1, 0.36, 1] },
    },
  }

  return (
    <section className="dark relative flex-1 overflow-hidden bg-[#06040F] pt-32 pb-24 text-white">
      <BackgroundEffects />
      <AnimatedParticles />

      <div className="relative z-10 container mx-auto flex flex-col items-center px-4 md:px-6">
        <motion.div
          initial="hidden"
          animate="show"
          variants={containerVariants}
          className="mx-auto mb-16 max-w-2xl text-center"
        >
          <motion.div
            variants={itemVariants}
            className="mx-auto mb-6 flex max-w-fit items-center justify-center space-x-2 rounded-full border border-fuchsia-500/30 bg-fuchsia-500/10 px-4 py-1.5 text-sm font-medium text-fuchsia-300 backdrop-blur-sm"
          >
            <span className="flex h-2 w-2 animate-pulse rounded-full bg-fuchsia-500 shadow-[0_0_10px_theme(colors.fuchsia.500)]"></span>
            <span>24/7 Support</span>
          </motion.div>

          <motion.h1
            variants={itemVariants}
            className="mb-6 text-5xl font-extrabold tracking-tight text-white drop-shadow-sm sm:text-6xl md:text-7xl"
          >
            Get in{" "}
            <span className="bg-gradient-to-r from-fuchsia-400 via-purple-500 to-indigo-500 bg-clip-text text-transparent">
              Touch
            </span>
          </motion.h1>
          <motion.p
            variants={itemVariants}
            className="text-lg leading-relaxed text-slate-400"
          >
            Have a question or need help? Send us a message and our expert team
            will get back to you within hours.
          </motion.p>
        </motion.div>

        <motion.div
          initial={{ opacity: 0, y: 60, filter: "blur(10px)" }}
          animate={{ opacity: 1, y: 0, filter: "blur(0px)" }}
          transition={{ duration: 1, delay: 0.4, ease: [0.22, 1, 0.36, 1] }}
          className="relative w-full max-w-2xl rounded-3xl border border-white/10 bg-[#06040F]/60 p-8 shadow-2xl shadow-purple-900/30 backdrop-blur-2xl sm:p-12"
        >
          {/* Inner Glow */}
          <div className="absolute -inset-0.5 -z-10 rounded-[24px] bg-gradient-to-b from-purple-500/20 to-transparent opacity-50 blur-sm"></div>

          <form
            className="relative z-10 space-y-6"
            onSubmit={(e) => e.preventDefault()}
          >
            <div className="grid grid-cols-1 gap-6 sm:grid-cols-2">
              <div className="space-y-2">
                <label
                  htmlFor="firstName"
                  className="text-sm font-medium text-slate-300"
                >
                  First Name
                </label>
                <input
                  type="text"
                  id="firstName"
                  className="h-14 w-full rounded-xl border border-white/10 bg-white/5 px-4 text-white transition-all placeholder:text-slate-600 focus:border-fuchsia-500/50 focus:ring-2 focus:ring-fuchsia-500/50 focus:outline-none"
                  placeholder="John"
                />
              </div>
              <div className="space-y-2">
                <label
                  htmlFor="lastName"
                  className="text-sm font-medium text-slate-300"
                >
                  Last Name
                </label>
                <input
                  type="text"
                  id="lastName"
                  className="h-14 w-full rounded-xl border border-white/10 bg-white/5 px-4 text-white transition-all placeholder:text-slate-600 focus:border-fuchsia-500/50 focus:ring-2 focus:ring-fuchsia-500/50 focus:outline-none"
                  placeholder="Doe"
                />
              </div>
            </div>

            <div className="space-y-2">
              <label
                htmlFor="email"
                className="text-sm font-medium text-slate-300"
              >
                Email Address
              </label>
              <input
                type="email"
                id="email"
                className="h-14 w-full rounded-xl border border-white/10 bg-white/5 px-4 text-white transition-all placeholder:text-slate-600 focus:border-fuchsia-500/50 focus:ring-2 focus:ring-fuchsia-500/50 focus:outline-none"
                placeholder="john@example.com"
              />
            </div>

            <div className="space-y-2">
              <label
                htmlFor="message"
                className="text-sm font-medium text-slate-300"
              >
                Message
              </label>
              <textarea
                id="message"
                rows={5}
                className="w-full resize-none rounded-xl border border-white/10 bg-white/5 p-4 text-white transition-all placeholder:text-slate-600 focus:border-fuchsia-500/50 focus:ring-2 focus:ring-fuchsia-500/50 focus:outline-none"
                placeholder="How can we help you today?"
              />
            </div>

            <Button
              type="submit"
              className="mt-4 h-14 w-full rounded-xl bg-gradient-to-r from-fuchsia-600 to-purple-600 text-base font-semibold text-white shadow-[0_0_20px_theme(colors.fuchsia.600/40)] transition-all hover:scale-[1.02] hover:shadow-[0_0_30px_theme(colors.fuchsia.600/60)]"
            >
              Send Message
            </Button>
          </form>
        </motion.div>
      </div>
    </section>
  )
}
```

### `src/components/animation/Particles.tsx`

```tsx
"use client"

import React, { useEffect, useRef } from "react"

// third-party
import { Renderer, Camera, Geometry, Program, Mesh } from "ogl"

interface ParticlesProps {
  particleCount?: number
  particleSpread?: number
  speed?: number
  particleColors?: string[]
  moveParticlesOnHover?: boolean
  particleHoverFactor?: number
  alphaParticles?: boolean
  particleBaseSize?: number
  sizeRandomness?: number
  cameraDistance?: number
  disableRotation?: boolean
  pixelRatio?: number
  className?: string
}

const defaultColors: string[] = ["#ffffff", "#ffffff", "#ffffff"]

const hexToRgb = (hex: string): [number, number, number] => {
  hex = hex.replace(/^#/, "")
  if (hex.length === 3) {
    hex = hex
      .split("")
      .map((c) => c + c)
      .join("")
  }
  const int = parseInt(hex.slice(0, 6), 16)
  const r = ((int >> 16) & 255) / 255
  const g = ((int >> 8) & 255) / 255
  const b = (int & 255) / 255
  return [r, g, b]
}

const vertex = /* glsl */ `
  attribute vec3 position;
  attribute vec4 random;
  attribute vec3 color;
  
  uniform mat4 modelMatrix;
  uniform mat4 viewMatrix;
  uniform mat4 projectionMatrix;
  uniform float uTime;
  uniform float uSpread;
  uniform float uBaseSize;
  uniform float uSizeRandomness;
  
  varying vec4 vRandom;
  varying vec3 vColor;
  
  void main() {
    vRandom = random;
    vColor = color;
    
    vec3 pos = position * uSpread;
    pos.z *= 10.0;
    
    vec4 mPos = modelMatrix * vec4(pos, 1.0);
    float t = uTime;
    mPos.x += sin(t * random.z + 6.28 * random.w) * mix(0.1, 1.5, random.x);
    mPos.y += sin(t * random.y + 6.28 * random.x) * mix(0.1, 1.5, random.w);
    mPos.z += sin(t * random.w + 6.28 * random.y) * mix(0.1, 1.5, random.z);
    
    vec4 mvPos = viewMatrix * mPos;

    if (uSizeRandomness == 0.0) {
      gl_PointSize = uBaseSize;
    } else {
      gl_PointSize = (uBaseSize * (1.0 + uSizeRandomness * (random.x - 0.5))) / length(mvPos.xyz);
    }
    
    gl_Position = projectionMatrix * mvPos;
    gl_Position = projectionMatrix * mvPos;
  }
`

const fragment = /* glsl */ `
  precision highp float;
  
  uniform float uTime;
  uniform float uAlphaParticles;
  varying vec4 vRandom;
  varying vec3 vColor;
  
  void main() {
    vec2 uv = gl_PointCoord.xy;
    float d = length(uv - vec2(0.5));
    
    if(uAlphaParticles < 0.5) {
      if(d > 0.5) {
        discard;
      }
      gl_FragColor = vec4(vColor + 0.2 * sin(uv.yxx + uTime + vRandom.y * 6.28), 1.0);
    } else {
      float circle = smoothstep(0.5, 0.4, d) * 0.8;
      gl_FragColor = vec4(vColor + 0.2 * sin(uv.yxx + uTime + vRandom.y * 6.28), circle);
    }
  }
`

const Particles: React.FC<ParticlesProps> = ({
  particleCount = 200,
  particleSpread = 10,
  speed = 0.1,
  particleColors,
  moveParticlesOnHover = false,
  particleHoverFactor = 1,
  alphaParticles = false,
  particleBaseSize = 100,
  sizeRandomness = 1,
  cameraDistance = 20,
  disableRotation = false,
  pixelRatio = 1,
  className,
}) => {
  const containerRef = useRef<HTMLDivElement>(null)
  const mouseRef = useRef<{ x: number; y: number }>({ x: 0, y: 0 })

  useEffect(() => {
    const container = containerRef.current
    if (!container) return

    const renderer = new Renderer({
      dpr: pixelRatio,
      depth: false,
      alpha: true,
    })
    const gl = renderer.gl
    container.appendChild(gl.canvas)
    gl.clearColor(0, 0, 0, 0)

    const camera = new Camera(gl, { fov: 15 })
    camera.position.set(0, 0, cameraDistance)

    const resize = () => {
      const width = container.clientWidth
      const height = container.clientHeight
      renderer.setSize(width, height)
      camera.perspective({ aspect: gl.canvas.width / gl.canvas.height })
    }
    window.addEventListener("resize", resize, false)
    resize()

    const handleMouseMove = (e: MouseEvent) => {
      const rect = container.getBoundingClientRect()
      const x = ((e.clientX - rect.left) / rect.width) * 2 - 1
      const y = -(((e.clientY - rect.top) / rect.height) * 2 - 1)
      mouseRef.current = { x, y }
    }

    if (moveParticlesOnHover) {
      container.addEventListener("mousemove", handleMouseMove)
    }

    const count = particleCount
    const positions = new Float32Array(count * 3)
    const randoms = new Float32Array(count * 4)
    const colors = new Float32Array(count * 3)
    const palette =
      particleColors && particleColors.length > 0
        ? particleColors
        : defaultColors

    for (let i = 0; i < count; i++) {
      let x: number, y: number, z: number, len: number
      do {
        x = Math.random() * 2 - 1
        y = Math.random() * 2 - 1
        z = Math.random() * 2 - 1
        len = x * x + y * y + z * z
      } while (len > 1 || len === 0)
      const r = Math.cbrt(Math.random())
      positions.set([x * r, y * r, z * r], i * 3)
      randoms.set(
        [Math.random(), Math.random(), Math.random(), Math.random()],
        i * 4
      )
      const col = hexToRgb(palette[Math.floor(Math.random() * palette.length)])
      colors.set(col, i * 3)
    }

    const geometry = new Geometry(gl, {
      position: { size: 3, data: positions },
      random: { size: 4, data: randoms },
      color: { size: 3, data: colors },
    })

    const program = new Program(gl, {
      vertex,
      fragment,
      uniforms: {
        uTime: { value: 0 },
        uSpread: { value: particleSpread },
        uBaseSize: { value: particleBaseSize * pixelRatio },
        uSizeRandomness: { value: sizeRandomness },
        uAlphaParticles: { value: alphaParticles ? 1 : 0 },
      },
      transparent: true,
      depthTest: false,
    })

    const particles = new Mesh(gl, { mode: gl.POINTS, geometry, program })

    let animationFrameId: number
    let lastTime = performance.now()
    let elapsed = 0

    const update = (t: number) => {
      animationFrameId = requestAnimationFrame(update)
      const delta = t - lastTime
      lastTime = t
      elapsed += delta * speed

      program.uniforms.uTime.value = elapsed * 0.001

      if (moveParticlesOnHover) {
        particles.position.x = -mouseRef.current.x * particleHoverFactor
        particles.position.y = -mouseRef.current.y * particleHoverFactor
      } else {
        particles.position.x = 0
        particles.position.y = 0
      }

      if (!disableRotation) {
        particles.rotation.x = Math.sin(elapsed * 0.0002) * 0.1
        particles.rotation.y = Math.cos(elapsed * 0.0005) * 0.15
        particles.rotation.z += 0.01 * speed
      }

      renderer.render({ scene: particles, camera })
    }

    animationFrameId = requestAnimationFrame(update)

    return () => {
      window.removeEventListener("resize", resize)
      if (moveParticlesOnHover) {
        container.removeEventListener("mousemove", handleMouseMove)
      }
      cancelAnimationFrame(animationFrameId)
      if (container.contains(gl.canvas)) {
        container.removeChild(gl.canvas)
      }
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [
    particleCount,
    particleSpread,
    speed,
    moveParticlesOnHover,
    particleHoverFactor,
    alphaParticles,
    particleBaseSize,
    sizeRandomness,
    cameraDistance,
    disableRotation,
    pixelRatio,
  ])

  return (
    <div ref={containerRef} className={`relative h-full w-full ${className}`} />
  )
}

export default Particles
```

### `src/components/animation/SeoAnimatedParticles.tsx`

```tsx
"use client"

// project-imports
import Particles from "@/components/animation/Particles"

export function SeoAnimatedParticles() {
  return (
    <div className="pointer-events-none fixed inset-0 z-0">
      <Particles
        className=""
        particleColors={["#ffffff"]}
        particleCount={100}
        particleSpread={10}
        speed={0.3}
        particleBaseSize={100}
        moveParticlesOnHover={false}
        alphaParticles
        disableRotation
        pixelRatio={1}
      />
    </div>
  )
}

export default SeoAnimatedParticles
```

### `src/components/animation/SeoBackgroundEffects.tsx`

```tsx
"use client"

export function SeoBackgroundEffects() {
  return (
    <>
      <style>{`
        @keyframes trail-v {
          0% { transform: translateY(-200px); opacity: 0; }
          10% { opacity: 1; }
          90% { opacity: 1; }
          100% { transform: translateY(1000px); opacity: 0; }
        }
        @keyframes trail-h {
          0% { transform: translateX(-300px); opacity: 0; }
          10% { opacity: 1; }
          90% { opacity: 1; }
          100% { transform: translateX(2000px); opacity: 0; }
        }
        .animate-trail-v {
          animation: trail-v linear infinite;
        }
        .animate-trail-h {
          animation: trail-h linear infinite;
        }
      `}</style>

      {/* 3D Perspective Grid Background */}
      <div className="absolute inset-0 top-0 -z-20 flex justify-center overflow-hidden [mask-image:linear-gradient(to_bottom,transparent_10%,black_40%,black_70%,transparent_100%)]">
        <div className="absolute top-[20%] h-[1000px] w-[2000px] origin-top [transform:perspective(500px)_rotateX(70deg)]">
          <svg
            width="2000"
            height="1000"
            className="absolute inset-0 opacity-80"
            xmlns="http://www.w3.org/2000/svg"
          >
            {/* Static Base Grid */}
            <pattern
              id="grid-pattern"
              width="60"
              height="60"
              patternUnits="userSpaceOnUse"
            >
              <path
                d="M 60 0 L 0 0 0 60"
                fill="none"
                stroke="rgba(168,85,247,0.3)"
                strokeWidth="1"
              />
            </pattern>
            <rect width="2000" height="1000" fill="url(#grid-pattern)" />
          </svg>

          {/* Vertical Trails */}
          {[120, 360, 600, 840, 1020, 1260, 1500, 1740, 1920].map((x, i) => (
            <div
              key={`v-${i}`}
              className="animate-trail-v absolute top-0 h-[200px] w-[2px] bg-gradient-to-b from-transparent via-fuchsia-500 to-transparent shadow-[0_0_15px_theme(colors.fuchsia.500)]"
              style={{
                left: x - 1,
                animationDelay: `${i * 0.8}s`,
                animationDuration: `${6 + (i % 4)}s`,
              }}
            ></div>
          ))}

          {/* Horizontal Trails */}
          {[180, 420, 660, 900].map((y, i) => (
            <div
              key={`h-${i}`}
              className="animate-trail-h absolute left-0 h-[2px] w-[300px] bg-gradient-to-r from-transparent via-purple-500 to-transparent shadow-[0_0_15px_theme(colors.purple.500)]"
              style={{
                top: y - 1,
                animationDelay: `${i * 1.2}s`,
                animationDuration: `${8 + (i % 3)}s`,
              }}
            ></div>
          ))}
        </div>
      </div>

      {/* Intense Horizontal Glow Effects Behind Dashboard */}
      <div className="absolute top-[55%] left-1/2 -z-10 h-[250px] w-[1400px] -translate-x-1/2 -translate-y-1/2 rounded-[100%] bg-fuchsia-700/60 blur-[100px]"></div>
      <div className="absolute top-[58%] left-1/2 -z-10 h-[100px] w-[1000px] -translate-x-1/2 -translate-y-1/2 rounded-[100%] bg-purple-500/80 blur-[60px]"></div>
      <div className="absolute top-[60%] left-1/2 -z-10 h-[30px] w-[800px] -translate-x-1/2 -translate-y-1/2 rounded-[100%] bg-white/80 blur-[30px]"></div>
      <div className="absolute top-[60%] left-1/2 -z-10 h-[10px] w-[600px] -translate-x-1/2 -translate-y-1/2 rounded-[100%] bg-white blur-[10px]"></div>
    </>
  )
}

export default SeoBackgroundEffects
```
