# Bento 7

> Bento 7 variant for block.

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

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

## Install

```bash
npx shadcn@latest add https://uiable.com/r/block-bento-7.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`, `react-qr-code`

## Files

### `src/components/uiable/blocks/bento/bento-7/bento-7.tsx`

```tsx
"use client"

// third-party
import { motion, useMotionValue, useMotionTemplate } from "framer-motion"
import QRCode from "react-qr-code"

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

// Spotlight wrapper for each bento card
function BentoCard({
  children,
  className,
  colSpan,
}: {
  children: React.ReactNode
  className?: string
  colSpan: string
}) {
  const mouseX = useMotionValue(0)
  const mouseY = useMotionValue(0)

  function handleMouseMove({
    currentTarget,
    clientX,
    clientY,
  }: React.MouseEvent) {
    const { left, top } = currentTarget.getBoundingClientRect()
    mouseX.set(clientX - left)
    mouseY.set(clientY - top)
  }

  return (
    <motion.div
      onMouseMove={handleMouseMove}
      initial={{ opacity: 0, y: 20 }}
      whileInView={{ opacity: 1, y: 0 }}
      viewport={{ once: true, margin: "-50px" }}
      whileHover={{ y: -5, scale: 1.01 }}
      transition={{ duration: 0.5 }}
      className={`group relative ${colSpan} flex flex-col justify-between overflow-hidden rounded-3xl border border-teal-800/60 bg-teal-900/40 p-6 shadow-xl backdrop-blur-xl transition-all duration-300 hover:border-teal-400/50 hover:shadow-teal-400/10 md:p-8 ${className}`}
    >
      <motion.div
        className="pointer-events-none absolute -inset-px rounded-3xl opacity-0 transition duration-300 group-hover:opacity-100"
        style={{
          background: useMotionTemplate`
            radial-gradient(
              400px circle at ${mouseX}px ${mouseY}px,
              rgba(45, 212, 191, 0.15),
              transparent 80%
            )
          `,
        }}
      />
      <div className="relative z-10 flex h-full flex-col justify-between">
        {children}
      </div>
    </motion.div>
  )
}

//  ------------------------------ | BENTO 7 | ------------------------------  //

export default function Bento7() {
  return (
    <section className="dark relative overflow-hidden bg-[#041a16] px-4 py-16 text-white md:px-8 md:py-32">
      <div className="absolute inset-0 z-10">
        <Particles
          particleCount={400}
          particleSpread={10}
          speed={0.26}
          particleColors={["#96f7e4"]}
          moveParticlesOnHover
          particleHoverFactor={0.8}
          alphaParticles={false}
          particleBaseSize={120}
          sizeRandomness={0.7}
          cameraDistance={15}
          disableRotation={false}
        />
      </div>
      <div className="pointer-events-none absolute top-[-20%] left-[-20%] h-[800px] w-[800px] rounded-full bg-teal-800/20 blur-[150px]" />
      <div className="pointer-events-none absolute right-[-20%] bottom-[-20%] h-[800px] w-[800px] rounded-full bg-emerald-950/40 blur-[150px]" />
      <div className="relative z-10 container mx-auto">
        <div className="mx-auto mb-12 max-w-2xl text-center md:mb-20">
          <span className="mb-6 inline-block rounded-full border border-teal-800/50 bg-teal-900/50 px-4 py-1.5 text-xs font-bold tracking-[0.2em] text-teal-400 uppercase shadow-sm">
            Startup Boost
          </span>
          <h2 className="text-4xl leading-tight font-extrabold tracking-tight text-white sm:text-5xl">
            Boost Your Startup to the{" "}
            <span className="bg-gradient-to-r from-teal-400 to-emerald-400 bg-clip-text text-transparent">
              Next Level
            </span>
          </h2>
          <p className="mt-6 text-lg leading-relaxed text-white/70">
            A command center for billing. Experience unmatched control over
            subscriptions, direct payouts, and sales charts.
          </p>
        </div>

        <div className="grid gap-5 md:gap-8 lg:grid-cols-12">
          <BentoCard colSpan="lg:col-span-7">
            <div>
              <span className="mb-2 block text-xs font-semibold tracking-wider text-teal-400 uppercase">
                Company Ledger
              </span>
              <h3 className="mb-4 text-2xl font-bold">Manage your company</h3>
              <p className="mb-8 max-w-md text-sm text-white/70">
                Synchronize internal ledger records and categorize expense types
                in real-time. Keep a clean treasury ledger without overhead.
              </p>
            </div>

            <div className="rounded-2xl border border-teal-800/50 bg-teal-950/80 p-5 shadow-inner backdrop-blur-sm transition-colors group-hover:bg-teal-950/90">
              <div className="mb-4 flex items-center justify-between border-b border-teal-800/50 pb-3">
                <span className="text-xs font-bold text-teal-400">
                  Transactions
                </span>
                <span className="flex items-center gap-1.5 text-[10px] text-teal-300">
                  <span className="relative flex h-2 w-2">
                    <span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-emerald-400 opacity-75"></span>
                    <span className="relative inline-flex h-2 w-2 rounded-full bg-emerald-500"></span>
                  </span>
                  Live feed
                </span>
              </div>
              <div className="space-y-4">
                {[
                  {
                    name: "Monthly Revenue (Stripe)",
                    amount: "+$14,250.00",
                    type: "income",
                    color: "text-emerald-400",
                  },
                  {
                    name: "SaaS Subscriptions",
                    amount: "-$840.00",
                    type: "expense",
                    color: "text-rose-400",
                  },
                ].map((tx, idx) => (
                  <div
                    key={idx}
                    className="flex items-center justify-between text-xs"
                  >
                    <div className="flex items-center gap-3">
                      <div
                        className={`h-2 w-2 rounded-full ${tx.type === "income" ? "bg-emerald-400 shadow-[0_0_8px_rgba(52,211,153,0.8)]" : "bg-rose-400 shadow-[0_0_8px_rgba(251,113,133,0.8)]"}`}
                      />
                      <span className="font-medium text-teal-50">
                        {tx.name}
                      </span>
                    </div>
                    <span className={`font-bold ${tx.color}`}>{tx.amount}</span>
                  </div>
                ))}
              </div>
            </div>
          </BentoCard>

          <BentoCard colSpan="lg:col-span-5">
            <div>
              <span className="mb-2 block text-xs font-semibold tracking-wider text-teal-400 uppercase">
                Instant Payouts
              </span>
              <h3 className="mb-4 text-2xl font-bold">Withdraw your funds</h3>
              <p className="mb-8 text-sm text-white/70">
                Unlock immediate liquidity. Withdraw funds direct to bank rails
                or settle on crypto cards via scan.
              </p>
            </div>

            <div className="flex items-center gap-5 rounded-2xl border border-teal-800/50 bg-teal-950/80 p-5 shadow-inner transition-colors group-hover:bg-teal-950/90">
              <div className="flex h-20 w-20 shrink-0 items-center justify-center rounded-xl bg-white p-1.5 shadow-lg shadow-white/10 transition-shadow group-hover:shadow-teal-400/20">
                <div className="flex h-full w-full items-center justify-center bg-white p-0.5">
                  <QRCode
                    value="https://uiable.com/"
                    style={{ height: "auto", maxWidth: "100%", width: "100%" }}
                    fgColor="#042f2e"
                  />
                </div>
              </div>
              <div>
                <span className="mb-1 block text-sm font-bold text-teal-50">
                  Settle via QR Code
                </span>
                <span className="block text-[11px] leading-relaxed text-teal-300">
                  Scan to instantly authorize local payout on card.
                </span>
              </div>
            </div>
          </BentoCard>

          <BentoCard colSpan="lg:col-span-5">
            <div>
              <span className="mb-2 block text-xs font-semibold tracking-wider text-teal-400 uppercase">
                Checkout
              </span>
              <h3 className="mb-4 text-2xl font-bold">
                Sell products & services
              </h3>
              <p className="mb-8 text-sm text-white/70">
                Turn every checkout into a seamless buying experience.
              </p>
            </div>

            <div className="space-y-4 rounded-2xl border border-teal-800/50 bg-teal-950/80 p-5 shadow-inner transition-colors group-hover:bg-teal-950/90">
              <div className="space-y-1.5">
                <label className="text-[10px] font-bold text-teal-500 uppercase">
                  Email Address
                </label>
                <div className="rounded-xl border border-teal-800/80 bg-teal-900/60 px-4 py-2.5 text-sm text-teal-100 shadow-inner">
                  customer@example.com
                </div>
              </div>
              <div className="space-y-1.5">
                <label className="text-[10px] font-bold text-teal-500 uppercase">
                  Card Number
                </label>
                <div className="flex items-center justify-between rounded-xl border border-teal-800/80 bg-teal-900/60 px-4 py-2.5 text-sm text-teal-100 shadow-inner">
                  <span className="tracking-widest">•••• •••• •••• 4242</span>
                  <span className="rounded bg-teal-800/80 px-2 py-1 text-[9px] font-bold text-teal-300 uppercase">
                    Visa
                  </span>
                </div>
              </div>
            </div>
          </BentoCard>

          <BentoCard colSpan="lg:col-span-7">
            <div>
              <span className="mb-2 block text-xs font-semibold tracking-wider text-teal-400 uppercase">
                Recurring Engine
              </span>
              <h3 className="mb-4 text-2xl font-bold">
                Launch a recurring model
              </h3>
            </div>

            <div className="rounded-2xl border border-teal-800/50 bg-teal-950/80 p-5 shadow-inner transition-colors group-hover:bg-teal-950/90">
              <div className="mb-6 flex items-center justify-between">
                <div>
                  <span className="mb-1 block text-[10px] font-bold tracking-wider text-teal-400 uppercase">
                    Monthly Recurring Revenue
                  </span>
                  <span className="text-2xl font-extrabold text-white">
                    $158,400
                  </span>
                </div>
                <span className="rounded-full border border-emerald-500/20 bg-emerald-500/10 px-3 py-1 text-xs font-bold text-emerald-400 shadow-[0_0_10px_rgba(16,185,129,0.2)]">
                  +28% this Mo
                </span>
              </div>
              <div className="relative mt-auto h-32 w-full">
                <svg
                  viewBox="0 0 400 120"
                  className="h-full w-full overflow-visible"
                  preserveAspectRatio="none"
                >
                  <defs>
                    <linearGradient id="chart-glow" x1="0" y1="0" x2="0" y2="1">
                      <stop offset="0%" stopColor="#10b981" stopOpacity="0.4" />
                      <stop offset="100%" stopColor="#10b981" stopOpacity="0" />
                    </linearGradient>
                  </defs>
                  <path
                    d="M 0,100 C 40,80 80,110 120,70 C 160,30 200,80 240,40 C 280,0 320,50 360,20 C 380,5 390,15 400,10 L 400,120 L 0,120 Z"
                    fill="url(#chart-glow)"
                    className="transition-opacity group-hover:opacity-80"
                  />
                  <path
                    d="M 0,100 C 40,80 80,110 120,70 C 160,30 200,80 240,40 C 280,0 320,50 360,20 C 380,5 390,15 400,10"
                    fill="none"
                    stroke="#34d399"
                    strokeWidth="2"
                    strokeLinecap="round"
                    className="drop-shadow-[0_0_8px_rgba(52,211,153,0.8)]"
                  />
                  <line
                    x1="0"
                    y1="30"
                    x2="400"
                    y2="30"
                    stroke="#0d9488"
                    strokeWidth="1"
                    strokeDasharray="4,6"
                    opacity="0.3"
                  />
                  <line
                    x1="0"
                    y1="70"
                    x2="400"
                    y2="70"
                    stroke="#0d9488"
                    strokeWidth="1"
                    strokeDasharray="4,6"
                    opacity="0.3"
                  />
                </svg>
              </div>
            </div>
          </BentoCard>
        </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
```
