Skip to content
Blocks

Integration Bento

An integration bento that brings tools and connected workflows together.

Open ↗

Make it yours.

Install the editable source and its dependencies into your React + Tailwind project.

Scroll horizontally for long lines
pnpm dlx shadcn@4.0.8 add http://localhost:3000/r/integration-bento.json

Use it

Scroll horizontally for long lines
"use client";
import { IntegrationBento } from "@/components/jez-ui/blocks/integration-bento";

export default function Example() {
  return (
    <>
      <IntegrationBento />
    </>
  );
}

Compose your own

These styled parts are included in the same install. Use children to build your layout; add className only when you want an override.

Scroll horizontally for long lines
"use client";
import {
  IntegrationBento,
  IntegrationBentoGrid,
  IntegrationBentoHeading,
} from "@/components/jez-ui/blocks/integration-bento";
export default function IntegrationBentoExample() {
  return (
    <IntegrationBento>
      <IntegrationBentoHeading />
      <IntegrationBentoGrid />
    </IntegrationBento>
  );
}
Scroll horizontally for long lines
import { IntegrationBento, IntegrationBentoHeading, IntegrationBentoGrid, IntegrationBentoFlow, IntegrationBentoNode, IntegrationBentoItems, IntegrationBentoItem } from "@/components/jez-ui/blocks/integration-bento";
Read the composition guide →

Props & types

Scroll horizontally for long lines
export type IntegrationBentoItem = {
  name: React.ReactNode;
  detail: React.ReactNode;
  initials: string;
  tone?: "sage" | "ink" | "paper";
};

export type IntegrationBentoProps = Omit<
  React.ComponentPropsWithoutRef<"section">,
  "title"
> & {
  title?: React.ReactNode;
  description?: React.ReactNode;
  items?: IntegrationBentoItem[];
};

Source

View 2 source files

registry/blocks/integration-bento.tsx

Scroll horizontally for long lines
"use client";

import * as React from "react";
import {
  ArrowUpRight,
  Check,
  CircleDashed,
  Send,
  Workflow,
} from "lucide-react";
import { cn } from "../ui/utils";

export type IntegrationBentoItem = {
  name: React.ReactNode;
  detail: React.ReactNode;
  initials: string;
  tone?: "sage" | "ink" | "paper";
};
const defaultItems: IntegrationBentoItem[] = [
  {
    name: "Project brief",
    detail: "One source of direction",
    initials: "PB",
    tone: "sage",
  },
  {
    name: "Design review",
    detail: "Feedback where it belongs",
    initials: "DR",
    tone: "paper",
  },
  {
    name: "Release checklist",
    detail: "Hand off with confidence",
    initials: "RC",
    tone: "ink",
  },
];
export type IntegrationBentoProps = Omit<
  React.ComponentPropsWithoutRef<"section">,
  "title"
> & {
  title?: React.ReactNode;
  description?: React.ReactNode;
  items?: IntegrationBentoItem[];
};
type IntegrationBentoModel = Required<
  Pick<IntegrationBentoProps, "title" | "description" | "items">
>;
const IntegrationBentoContext =
  React.createContext<IntegrationBentoModel | null>(null);
function useIntegrationBento() {
  const context = React.useContext(IntegrationBentoContext);
  if (!context)
    throw new Error("IntegrationBento parts must be inside IntegrationBento.");
  return context;
}
export const IntegrationBento = React.forwardRef<
  HTMLElement,
  IntegrationBentoProps
>(function IntegrationBento(
  {
    title = "Your tools should pass the right context along.",
    description = "Connect the moments that make work feel joined up, without asking people to duplicate the important bit.",
    items = defaultItems,
    className,
    children,
    ...props
  },
  ref,
) {
  return (
    <IntegrationBentoContext.Provider value={{ title, description, items }}>
      <section
        ref={ref}
        data-slot="integration-bento"
        className={cn("py-12 md:py-20", className)}
        {...props}
      >
        {children !== undefined ? (
          children
        ) : (
          <>
            <IntegrationBentoHeading />
            <IntegrationBentoGrid />
          </>
        )}
      </section>
    </IntegrationBentoContext.Provider>
  );
});
export const IntegrationBentoHeading = React.forwardRef<
  HTMLDivElement,
  React.ComponentPropsWithoutRef<"div">
>(function IntegrationBentoHeading({ children, className, ...props }, ref) {
  const { title, description } = useIntegrationBento();
  return (
    <div
      ref={ref}
      data-slot="integration-bento-heading"
      className={cn(
        "mb-8 grid gap-5 md:mb-12 md:grid-cols-2 md:items-end",
        className,
      )}
      {...props}
    >
      {children !== undefined ? (
        children
      ) : (
        <>
          <h2 className="max-w-xl text-balance text-4xl leading-[1.05] tracking-[-.035em] md:text-6xl">
            {title}
          </h2>
          <p className="max-w-md text-sm leading-relaxed text-muted-foreground md:justify-self-end">
            {description}
          </p>
        </>
      )}
    </div>
  );
});
export const IntegrationBentoGrid = React.forwardRef<
  HTMLDivElement,
  React.ComponentPropsWithoutRef<"div">
>(function IntegrationBentoGrid({ children, className, ...props }, ref) {
  const { items } = useIntegrationBento();
  return (
    <div
      ref={ref}
      data-slot="integration-bento-grid"
      className={cn("grid gap-3 md:grid-cols-12", className)}
      {...props}
    >
      {children !== undefined ? (
        children
      ) : (
        <>
          <IntegrationBentoFlow />
          <IntegrationBentoItems items={items} />
        </>
      )}
    </div>
  );
});
export const IntegrationBentoFlow = React.forwardRef<
  HTMLDivElement,
  React.ComponentPropsWithoutRef<"div">
>(function IntegrationBentoFlow({ children, className, ...props }, ref) {
  return (
    <div
      ref={ref}
      data-slot="integration-bento-flow"
      className={cn(
        "relative min-h-80 overflow-hidden rounded-xl bg-[#36483e] p-6 text-white md:col-span-7 md:p-8",
        className,
      )}
      {...props}
    >
      {children !== undefined ? (
        children
      ) : (
        <>
          <div className="flex items-center justify-between">
            <span className="inline-flex items-center gap-2 text-xs text-white/65">
              <Workflow size={15} /> Connected flow
            </span>
            <ArrowUpRight size={17} />
          </div>
          <div className="relative mt-12 grid gap-3 sm:grid-cols-3">
            <IntegrationBentoNode
              label="Brief"
              icon={<CircleDashed size={16} />}
            />
            <IntegrationBentoNode label="Review" icon={<Check size={16} />} />
            <IntegrationBentoNode label="Release" icon={<Send size={16} />} />
          </div>
          <p className="absolute bottom-7 left-6 max-w-sm text-2xl leading-tight tracking-[-.03em] md:left-8">
            A handoff should carry the decision, not just a link.
          </p>
          <svg
            aria-hidden
            className="absolute right-0 top-16 hidden h-32 w-1/2 text-white/20 sm:block"
            viewBox="0 0 300 120"
            fill="none"
          >
            <path
              d="M0 60H72C95 60 104 18 130 18H300"
              stroke="currentColor"
              strokeWidth="1"
              strokeDasharray="4 5"
            />
          </svg>
        </>
      )}
    </div>
  );
});
export const IntegrationBentoNode = React.forwardRef<
  HTMLDivElement,
  React.ComponentPropsWithoutRef<"div"> & {
    label: React.ReactNode;
    icon: React.ReactNode;
  }
>(function IntegrationBentoNode({ label, icon, className, ...props }, ref) {
  return (
    <div
      ref={ref}
      data-slot="integration-bento-node"
      className={cn(
        "relative z-10 flex items-center gap-2 rounded-lg bg-white/10 p-3 text-xs font-medium",
        className,
      )}
      {...props}
    >
      <span className="grid size-7 place-items-center rounded-full bg-white/15">
        {icon}
      </span>
      {label}
    </div>
  );
});
export const IntegrationBentoItems = React.forwardRef<
  HTMLDivElement,
  React.ComponentPropsWithoutRef<"div"> & { items?: IntegrationBentoItem[] }
>(function IntegrationBentoItems(
  { items, children, className, ...props },
  ref,
) {
  const model = useIntegrationBento();
  const actualItems = items ?? model.items;
  return (
    <div
      ref={ref}
      data-slot="integration-bento-items"
      className={cn(
        "grid gap-3 sm:grid-cols-3 md:col-span-5 md:grid-cols-1",
        className,
      )}
      {...props}
    >
      {children !== undefined
        ? children
        : actualItems.map((item) => (
            <IntegrationBentoItem key={String(item.name)} item={item} />
          ))}
    </div>
  );
});
export const IntegrationBentoItem = React.forwardRef<
  HTMLDivElement,
  React.ComponentPropsWithoutRef<"div"> & { item: IntegrationBentoItem }
>(function IntegrationBentoItem({ item, children, className, ...props }, ref) {
  const tone =
    item.tone === "ink"
      ? "bg-[#191b19] text-[#f4f4ec]"
      : item.tone === "sage"
        ? "bg-[#d9e3cd]"
        : "bg-muted";
  return (
    <div
      ref={ref}
      data-slot="integration-bento-item"
      className={cn(
        "group flex min-h-24 items-center gap-4 rounded-xl p-5",
        tone,
        className,
      )}
      {...props}
    >
      {children !== undefined ? (
        children
      ) : (
        <>
          <span
            className={cn(
              "grid size-10 shrink-0 place-items-center rounded-full text-xs font-medium",
              item.tone === "ink" ? "bg-white/10" : "bg-background",
            )}
          >
            {item.initials}
          </span>
          <div className="min-w-0 flex-1">
            <h3 className="text-sm font-medium">{item.name}</h3>
            <p
              className={cn(
                "mt-1 text-xs",
                item.tone === "ink"
                  ? "text-[#b5baae]"
                  : "text-muted-foreground",
              )}
            >
              {item.detail}
            </p>
          </div>
          <ArrowUpRight
            size={16}
            className="shrink-0 opacity-55 transition-transform group-hover:-translate-y-0.5 group-hover:translate-x-0.5"
          />
        </>
      )}
    </div>
  );
});
IntegrationBento.displayName = "IntegrationBento";

registry/ui/utils.ts

Scroll horizontally for long lines
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...values: ClassValue[]) {
  return twMerge(clsx(values));
}

Dependencies

lucide-react@0.577.0 · clsx@2.1.1 · tailwind-merge@3.5.0

Accessibility & behaviour

Provide meaningful labels and preserve keyboard focus styles. Check contrast when customising theme colours.

Read the accessibility and performance guide →