All components

Liquid Metal Hero

Liquid Metal Hero is a React and Tailwind CSS layout component from Cult UI, licensed MIT. Copy it and paste it into your project.

Cult UI MIT layout

What it is

Liquid Metal Hero is a React and Tailwind CSS layout component from Cult UI, licensed MIT. Copy it and paste it into your project.

How to use it

Open it in the catalogue, press copy, and paste it into your project. The prompt you copy carries the code and the rules that tell your agent to reproduce it without changing anything.

Open in the catalogue

Five free copies a month. No card.

Needs

npm i @paper-design/shaders-react

Licence

This component comes from Cult UI and is redistributed under the MIT licence, which permits commercial use. The copyright notice travels with the code.

Liquid Metal Hero

apps/www/registry/default/ui/hero-liquid-metal.tsx
"use client"

import * as React from "react"
import type { SVGProps } from "react"
import { LiquidMetal } from "@paper-design/shaders-react"

import { cn } from "@/lib/utils"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"

const MemoizedLiquidMetal = React.memo(LiquidMetal)

type LiquidMetalProps = React.ComponentProps<typeof LiquidMetal>
type LiquidMetalIcon = React.ComponentType<SVGProps<SVGSVGElement>>

export interface HeroLiquidMetalTechItem {
  name: string
  version?: string
  icon?: LiquidMetalIcon
}

export interface HeroLiquidMetalCTAProps {
  label: React.ReactNode
  href: string
  target?: React.HTMLAttributeAnchorTarget
  rel?: string
  onClick?: React.MouseEventHandler<HTMLAnchorElement>
  className?: string
  buttonClassName?: string
}

/** LiquidMetal shader props that can be passed at the root for convenience */
export type HeroLiquidMetalShaderOverrides = Partial<
  Pick<
    LiquidMetalProps,
    | "width"
    | "height"
    | "image"
    | "colorBack"
    | "colorTint"
    | "shape"
    | "repetition"
    | "softness"
    | "shiftRed"
    | "shiftBlue"
    | "distortion"
    | "contour"
    | "angle"
    | "speed"
    | "frame"
    | "scale"
    | "rotation"
    | "offsetX"
    | "offsetY"
    | "fit"
    | "originX"
    | "originY"
    | "minPixelRatio"
    | "maxPixelCount"
  >
>

export interface HeroLiquidMetalRootProps
  extends Omit<React.ComponentPropsWithoutRef<"section">, "title">,
    HeroLiquidMetalShaderOverrides {
  srTitle?: string
  title?: React.ReactNode
  subtitle?: React.ReactNode
  description?: React.ReactNode
  showCta?: boolean
  ctaProps?: Partial<HeroLiquidMetalCTAProps>
  renderCta?: (defaultCta: React.ReactNode) => React.ReactNode
  showBadges?: boolean
  techStack?: HeroLiquidMetalTechItem[]
  renderBadge?: (
    tech: HeroLiquidMetalTechItem,
    index: number,
    defaultBadge: React.ReactNode
  ) => React.ReactNode
  desktopShaderProps?: Partial<LiquidMetalProps>
  mobileShaderProps?: Partial<LiquidMetalProps>
}

export interface HeroLiquidMetalHeadingProps
  extends Omit<React.ComponentPropsWithoutRef<"div">, "title"> {
  title?: React.ReactNode
  subtitle?: React.ReactNode
  headingClassName?: string
}

export interface HeroLiquidMetalDescriptionProps
  extends React.ComponentPropsWithoutRef<"div"> {
  description?: React.ReactNode
  descriptionClassName?: string
}

export interface HeroLiquidMetalActionsProps
  extends React.ComponentPropsWithoutRef<"div"> {
  showCta?: boolean
  ctaProps?: Partial<HeroLiquidMetalCTAProps>
  renderCta?: (defaultCta: React.ReactNode) => React.ReactNode
}

export interface HeroLiquidMetalBadgesProps
  extends React.ComponentPropsWithoutRef<"div"> {
  showBadges?: boolean
  techStack?: HeroLiquidMetalTechItem[]
  renderBadge?: (
    tech: HeroLiquidMetalTechItem,
    index: number,
    defaultBadge: React.ReactNode
  ) => React.ReactNode
}

export interface HeroLiquidMetalVisualProps
  extends React.ComponentPropsWithoutRef<"div"> {
  desktopShaderProps?: Partial<LiquidMetalProps>
  desktopClassName?: string
}

export interface HeroLiquidMetalMobileVisualProps
  extends React.ComponentPropsWithoutRef<"div"> {
  mobileShaderProps?: Partial<LiquidMetalProps>
}

export interface HeroLiquidMetalProps extends HeroLiquidMetalRootProps {
  containerClassName?: string
  contentClassName?: string
  headingWrapClassName?: string
  headingClassName?: string
  descriptionWrapClassName?: string
  descriptionClassName?: string
  ctaWrapClassName?: string
  badgesWrapClassName?: string
  visualClassName?: string
  mobileVisualClassName?: string
}

interface HeroLiquidMetalContextValue {
  srTitle: string
  title: React.ReactNode
  subtitle: React.ReactNode
  description: React.ReactNode
  showCta: boolean
  mergedCtaProps: HeroLiquidMetalCTAProps
  renderCta?: (defaultCta: React.ReactNode) => React.ReactNode
  showBadges: boolean
  techStack: HeroLiquidMetalTechItem[]
  renderBadge?: HeroLiquidMetalBadgesProps["renderBadge"]
  mergedDesktopShaderProps: Partial<LiquidMetalProps>
  mergedMobileShaderProps: Partial<LiquidMetalProps>
}

const defaultDesktopShaderProps: Partial<LiquidMetalProps> = {
  width: 1280,
  height: 720,
  image: "/cult-icon.svg",
  colorBack: "#ffffff00",
  colorTint: "#2c5d72",
  shape: undefined,
  repetition: 6,
  softness: 0.8,
  shiftRed: 1,
  shiftBlue: -1,
  distortion: 0.4,
  contour: 0.4,
  angle: 0,
  speed: 1,
  scale: 0.6,
  fit: "contain",
}

const defaultMobileShaderProps: Partial<LiquidMetalProps> = {
  image: "/cult-icon.svg",
  colorBack: "#ffffff00",
  colorTint: "#2c5d72",
  shape: undefined,
  repetition: 6,
  softness: 0.8,
  shiftRed: 1,
  shiftBlue: -1,
  distortion: 0.4,
  contour: 0.4,
  angle: 0,
  speed: 1,
  scale: 0.68,
  fit: "contain",
  style: { height: "100%", width: "100%" },
}

const defaultCtaProps: HeroLiquidMetalCTAProps = {
  label: "Check it out today",
  href: "https://aisdkagents.com",
  target: "_blank",
  rel: "noopener noreferrer",
}

const defaultDescription = (
  <>
    Full-stack vercel ai sdk patterns for workflows, tool calling, and agent
    orchestration. Built with{" "}
    <span className="font-medium tracking-tight">ai sdk v6</span> and{" "}
    <span className="font-medium tracking-tight">shadcn/ui</span>.
    <span className="hidden sm:inline"> Headless, themable, practical.</span>
  </>
)

const defaultTechStack: HeroLiquidMetalTechItem[] = [
  {
    name: "Next.js",
    version: "v16",
    icon: NextjsIcon,
  },
  {
    name: "AI SDK",
    version: "v6",
    icon: AISDKIcon,
  },
]

const HeroLiquidMetalContext = React.createContext<
  HeroLiquidMetalContextValue | undefined
>(undefined)

function useHeroLiquidMetalContext() {
  const context = React.useContext(HeroLiquidMetalContext)
  if (!context) {
    throw new Error(
      "HeroLiquidMetal components must be used within HeroLiquidMetalRoot"
    )
  }
  return context
}

export function useHeroLiquidMetal() {
  return useHeroLiquidMetalContext()
}

export const HeroLiquidMetalRoot = React.forwardRef<
  HTMLElement,
  HeroLiquidMetalRootProps
>(({ className, children, srTitle = "AI SDK Agents", title = <span className="">
      AI SDK Agents
    </span>, subtitle = "Copy and Paste", description = defaultDescription, showCta = true, ctaProps, renderCta, showBadges = true, techStack = defaultTechStack, renderBadge, desktopShaderProps, mobileShaderProps, width, height, image, colorBack, colorTint, shape, repetition, softness, shiftRed, shiftBlue, distortion, contour, angle, speed, frame, scale, rotation, offsetX, offsetY, fit, originX, originY, minPixelRatio, maxPixelCount, ...props }, ref) => {
  const mergedCtaProps = React.useMemo(
    () => ({
      ...defaultCtaProps,
      ...ctaProps,
    }),
    [ctaProps]
  )

  const shaderOverrides = React.useMemo((): Partial<LiquidMetalProps> => {
    const overrides: Partial<LiquidMetalProps> = {}
    if (width !== undefined) overrides.width = width
    if (height !== undefined) overrides.height = height
    if (image !== undefined) overrides.image = image
    if (colorBack !== undefined) overrides.colorBack = colorBack
    if (colorTint !== undefined) overrides.colorTint = colorTint
    if (shape !== undefined) overrides.shape = shape
    if (repetition !== undefined) overrides.repetition = repetition
    if (softness !== undefined) overrides.softness = softness
    if (shiftRed !== undefined) overrides.shiftRed = shiftRed
    if (shiftBlue !== undefined) overrides.shiftBlue = shiftBlue
    if (distortion !== undefined) overrides.distortion = distortion
    if (contour !== undefined) overrides.contour = contour
    if (angle !== undefined) overrides.angle = angle
    if (speed !== undefined) overrides.speed = speed
    if (frame !== undefined) overrides.frame = frame
    if (scale !== undefined) overrides.scale = scale
    if (rotation !== undefined) overrides.rotation = rotation
    if (offsetX !== undefined) overrides.offsetX = offsetX
    if (offsetY !== undefined) overrides.offsetY = offsetY
    if (fit !== undefined) overrides.fit = fit
    if (originX !== undefined) overrides.originX = originX
    if (originY !== undefined) overrides.originY = originY
    if (minPixelRatio !== undefined) overrides.minPixelRatio = minPixelRatio
    if (maxPixelCount !== undefined) overrides.maxPixelCount = maxPixelCount
    return overrides
  }, [
    width,
    height,
    image,
    colorBack,
    colorTint,
    shape,
    repetition,
    softness,
    shiftRed,
    shiftBlue,
    distortion,
    contour,
    angle,
    speed,
    frame,
    scale,
    rotation,
    offsetX,
    offsetY,
    fit,
    originX,
    originY,
    minPixelRatio,
    maxPixelCount,
  ])

  const mergedDesktopShaderProps = React.useMemo(
    () => ({
      ...defaultDesktopShaderProps,
      ...shaderOverrides,
      ...desktopShaderProps,
    }),
    [shaderOverrides, desktopShaderProps]
  )

  const mergedMobileShaderProps = React.useMemo(
    () => ({
      ...defaultMobileShaderProps,
      ...shaderOverrides,
      ...mobileShaderProps,
      style: {
        ...(defaultMobileShaderProps.style as React.CSSProperties),
        ...(mobileShaderProps?.style as React.CSSProperties | undefined),
      },
    }),
    [shaderOverrides, mobileShaderProps]
  )

  const contextValue = React.useMemo<HeroLiquidMetalContextValue>(
    () => ({
      srTitle,
      title,
      subtitle,
      description,
      showCta,
      mergedCtaProps,
      renderCta,
      showBadges,
      techStack,
      renderBadge,
      mergedDesktopShaderProps,
      mergedMobileShaderProps,
    }),
    [
      srTitle,
      title,
      subtitle,
      description,
      showCta,
      mergedCtaProps,
      renderCta,
      showBadges,
      techStack,
      renderBadge,
      mergedDesktopShaderProps,
      mergedMobileShaderProps,
    ]
  )

  return (
    <HeroLiquidMetalContext.Provider value={contextValue}>
      <section
        className={cn("relative h-full w-full overflow-hidden", className)}
        data-slot="hero-liquid-metal-root"
        ref={ref}
        {...props}
      >
        <h1 className="sr-only">{srTitle}</h1>
        {children}
      </section>
    </HeroLiquidMetalContext.Provider>
  )
})
HeroLiquidMetalRoot.displayName = "HeroLiquidMetalRoot"

export function HeroLiquidMetalContainer({
  className,
  ...props
}: React.ComponentPropsWithoutRef<"div">) {
  return (
    <div
      className={cn(
        "container relative z-10 grid gap-6 pb-16 sm:gap-8 sm:pb-20 lg:grid-cols-[1fr_minmax(300px,500px)] lg:items-center lg:gap-12 lg:pb-24 xl:grid-cols-[1fr_1fr]",
        className
      )}
      data-slot="hero-liquid-metal-container"
      {...props}
    />
  )
}

export function HeroLiquidMetalContent({
  className,
  ...props
}: React.ComponentPropsWithoutRef<"div">) {
  return (
    <div
      className={cn(
        "flex flex-col justify-center gap-4 text-balance sm:gap-5 sm:px-4 md:px-8 lg:gap-6 lg:pr-0 lg:pl-4 xl:pl-8 2xl:pl-0",
        className
      )}
      data-slot="hero-liquid-metal-content"
      {...props}
    />
  )
}

export function HeroLiquidMetalHeading({
  className,
  title,
  subtitle,
  headingClassName,
  children,
  ...props
}: HeroLiquidMetalHeadingProps) {
  const context = useHeroLiquidMetalContext()
  const resolvedTitle = title ?? context.title
  const resolvedSubtitle = subtitle ?? context.subtitle

  return (
    <div
      className={cn("pt-4 text-center sm:pt-6 lg:pt-0 lg:text-left", className)}
      data-slot="hero-liquid-metal-heading-wrap"
      {...props}
    >
      {children ?? (
        <div className="relative">
          <h2
            className={cn(
              "relative mb-0 text-balance font-medium  text-3xl tracking-[-0.04em] sm:text-4xl md:text-5xl lg:tracking-[-0.06em] xl:text-6xl 2xl:text-7xl",
              headingClassName
            )}
            data-slot="hero-liquid-metal-heading"
          >
            {resolvedTitle} <br />
            {resolvedSubtitle}
          </h2>
        </div>
      )}
    </div>
  )
}

export function HeroLiquidMetalDescription({
  className,
  description,
  descriptionClassName,
  children,
  ...props
}: HeroLiquidMetalDescriptionProps) {
  const context = useHeroLiquidMetalContext()
  const resolvedDescription = description ?? context.description

  return (
    <div
      className={cn(
        "mx-auto max-w-xl pb-2 text-center sm:pb-4 lg:mx-0 lg:max-w-none lg:pb-0 lg:text-left",
        className
      )}
      data-slot="hero-liquid-metal-description-wrap"
      {...props}
    >
      {children ?? (
        <p
          className={cn(
            "mt-0 mb-0 font-sans text-foreground/70 text-sm sm:text-base md:text-foreground/80 lg:text-lg xl:text-xl",
            descriptionClassName
          )}
          data-slot="hero-liquid-metal-description"
        >
          {resolvedDescription}
        </p>
      )}
    </div>
  )
}

export function HeroLiquidMetalActions({
  className,
  showCta,
  ctaProps,
  renderCta,
  children,
  ...props
}: HeroLiquidMetalActionsProps) {
  const context = useHeroLiquidMetalContext()
  const shouldShowCta = showCta ?? context.showCta
  const resolvedCtaProps = { ...context.mergedCtaProps, ...ctaProps }
  const resolvedRenderCta = renderCta ?? context.renderCta

  if (!shouldShowCta) {
    return null
  }

  const defaultCta = <HeroLiquidMetalCTA {...resolvedCtaProps} />

  return (
    <div
      className={cn("flex justify-center lg:justify-start", className)}
      data-slot="hero-liquid-metal-cta-wrap"
      {...props}
    >
      {children ??
        (resolvedRenderCta ? resolvedRenderCta(defaultCta) : defaultCta)}
    </div>
  )
}

export function HeroLiquidMetalCTA({
  label,
  href,
  target,
  rel,
  onClick,
  className,
  buttonClassName,
}: HeroLiquidMetalCTAProps) {
  return (
    <div
      className={cn(
        "flex items-center justify-center gap-4 pb-4 md:pb-0",
        className
      )}
      data-slot="hero-liquid-metal-cta"
    >
      <Button asChild className={cn("", buttonClassName)} size="lg">
        <a href={href} onClick={onClick} rel={rel} target={target}>
          {label}
        </a>
      </Button>
    </div>
  )
}

export function HeroLiquidMetalBadges({
  className,
  showBadges,
  techStack,
  renderBadge,
  ...props
}: HeroLiquidMetalBadgesProps) {
  const context = useHeroLiquidMetalContext()
  const shouldShowBadges = showBadges ?? context.showBadges
  const resolvedTechStack = techStack ?? context.techStack
  const resolvedRenderBadge = renderBadge ?? context.renderBadge

  if (!shouldShowBadges) {
    return null
  }

  return (
    <div
      className={cn(
        "flex flex-wrap items-center justify-center gap-2.5",
        className
      )}
      data-slot="hero-liquid-metal-badges"
      {...props}
    >
      {resolvedTechStack.map((tech, index) => {
        const Icon = tech.icon
        const defaultBadge = (
          <Badge
            className={cn(
              "group relative px-3.5 py-1.5 font-medium transition-all duration-150",
              "border border-border/50 bg-card text-card-foreground",
              "shadow-[0_1px_3px_rgba(0,0,0,0.08)] dark:shadow-[0_1px_3px_rgba(0,0,0,0.3)]",
              "hover:-translate-y-px hover:shadow-[0_2px_8px_rgba(0,0,0,0.12)] dark:hover:shadow-[0_2px_8px_rgba(0,0,0,0.4)]"
            )}
            data-slot="hero-liquid-metal-badge"
            key={tech.name}
            variant="outline"
          >
            {Icon ? <Icon className="size-3.5 opacity-80 mr-1" /> : null}
            <span className="font-semibold tracking-tight">{tech.name}</span>
            {tech.version ? (
              <span className="font-mono text-xs opacity-50">
                {tech.version}
              </span>
            ) : null}
          </Badge>
        )

        if (resolvedRenderBadge) {
          return (
            <React.Fragment key={tech.name}>
              {resolvedRenderBadge(tech, index, defaultBadge)}
            </React.Fragment>
          )
        }

        return defaultBadge
      })}
    </div>
  )
}

export function HeroLiquidMetalVisual({
  className,
  desktopClassName,
  desktopShaderProps,
  ...props
}: HeroLiquidMetalVisualProps) {
  const context = useHeroLiquidMetalContext()
  const resolvedDesktopShaderProps = {
    ...context.mergedDesktopShaderProps,
    ...desktopShaderProps,
  }

  return (
    <div
      className={cn(
        "relative hidden h-[350px] lg:block lg:h-[400px] xl:h-[500px]",
        className
      )}
      data-slot="hero-liquid-metal-visual"
      {...props}
    >
      <div
        className={cn(
          "absolute inset-0 flex items-center justify-center overflow-hidden rounded-full",
          desktopClassName
        )}
        data-slot="hero-liquid-metal-desktop"
      >
        <MemoizedLiquidMetal
          {...resolvedDesktopShaderProps}
          image={
            resolvedDesktopShaderProps.image ??
            (defaultDesktopShaderProps.image as string)
          }
        />
      </div>
    </div>
  )
}

export function HeroLiquidMetalMobileVisual({
  className,
  mobileShaderProps,
  ...props
}: HeroLiquidMetalMobileVisualProps) {
  const context = useHeroLiquidMetalContext()
  const resolvedMobileShaderProps = {
    ...context.mergedMobileShaderProps,
    ...mobileShaderProps,
    style: {
      ...(context.mergedMobileShaderProps.style as React.CSSProperties),
      ...(mobileShaderProps?.style as React.CSSProperties | undefined),
    },
  }

  return (
    <div
      className={cn(
        "pointer-events-none absolute inset-x-0 -bottom-24 -z-10 h-[360px] overflow-hidden lg:hidden",
        className
      )}
      data-slot="hero-liquid-metal-mobile"
      {...props}
    >
      <div className="absolute inset-x-0 top-0 z-10 h-56 bg-gradient-to-b from-background via-background/95 to-transparent" />
      <MemoizedLiquidMetal
        {...resolvedMobileShaderProps}
        image={
          resolvedMobileShaderProps.image ??
          (defaultMobileShaderProps.image as string)
        }
      />
    </div>
  )
}

export function HeroLiquidMetal({
  containerClassName,
  contentClassName,
  headingWrapClassName,
  headingClassName,
  descriptionWrapClassName,
  descriptionClassName,
  ctaWrapClassName,
  badgesWrapClassName,
  visualClassName,
  mobileVisualClassName,
  ...props
}: HeroLiquidMetalProps) {
  return (
    <HeroLiquidMetalRoot {...props}>
      <HeroLiquidMetalContainer className={containerClassName}>
        <HeroLiquidMetalContent className={contentClassName}>
          <HeroLiquidMetalHeading
            className={headingWrapClassName}
            headingClassName={headingClassName}
          />
          <HeroLiquidMetalDescription
            className={descriptionWrapClassName}
            descriptionClassName={descriptionClassName}
          />
          <HeroLiquidMetalActions className={ctaWrapClassName} />
          <div
            className={cn(
              "hidden lg:flex justify-center lg:justify-start",
              badgesWrapClassName
            )}
            data-slot="hero-liquid-metal-badges-wrap"
          >
            <HeroLiquidMetalBadges />
          </div>
        </HeroLiquidMetalContent>
        <HeroLiquidMetalVisual className={visualClassName} />
      </HeroLiquidMetalContainer>
      <HeroLiquidMetalMobileVisual className={mobileVisualClassName} />
    </HeroLiquidMetalRoot>
  )
}

export function AISDKIcon(props: SVGProps<SVGSVGElement>) {
  return (
    <svg
      color="currentcolor"
      data-testid="geist-icon"
      height="1em"
      strokeLinejoin="round"
      viewBox="0 0 16 16"
      width="1em"
      {...props}
    >
      <title>AI SDK</title>
      <path
        d="M2.5.5V0h1v.5a2 2 0 002 2H6v1h-.5a2 2 0 00-2 2V6h-1v-.5a2 2 0 00-2-2H0v-1h.5a2 2 0 002-2zM14.5 4.5V5h-1v-.5a1 1 0 00-1-1H12v-1h.5a1 1 0 001-1V1h1v.5a1 1 0 001 1h.5v1h-.5a1 1 0 00-1 1zM8.407 4.93L8.5 4h1l.093.93a5 5 0 004.478 4.477L15 9.5v1l-.93.093a5 5 0 00-4.477 4.478L9.5 16h-1l-.093-.93a5 5 0 00-4.478-4.477L3 10.5v-1l.93-.093A5 5 0 008.406 4.93z"
        fill="currentColor"
      />
    </svg>
  )
}

export function NextjsIcon(props: SVGProps<SVGSVGElement>) {
  const id = React.useId()
  const maskId = `${id}-mask`
  const paint0Id = `${id}-paint0`
  const paint1Id = `${id}-paint1`

  return (
    <svg
      fill="none"
      height="1em"
      viewBox="0 0 180 180"
      width="1em"
      xmlns="http://www.w3.org/2000/svg"
      {...props}
    >
      <title>Next.js</title>
      <mask
        height={180}
        id={maskId}
        maskUnits="userSpaceOnUse"
        style={{
          maskType: "alpha",
        }}
        width={180}
        x={0}
        y={0}
      >
        <circle cx={90} cy={90} fill="black" r={90} />
      </mask>
      <g mask={`url(#${maskId})`}>
        <circle
          cx={90}
          cy={90}
          fill="black"
          r={87}
          stroke="white"
          strokeWidth={6}
        />
        <path
          d="M149.508 157.52L69.142 54H54V125.97H66.1136V69.3836L139.999 164.845C143.333 162.614 146.509 160.165 149.508 157.52Z"
          fill={`url(#${paint0Id})`}
        />
        <rect
          fill={`url(#${paint1Id})`}
          height={72}
          width={12}
          x={115}
          y={54}
        />
      </g>
      <defs>
        <linearGradient
          gradientUnits="userSpaceOnUse"
          id={paint0Id}
          x1={109}
          x2={144.5}
          y1={116.5}
          y2={160.5}
        >
          <stop stopColor="white" />
          <stop offset={1} stopColor="white" stopOpacity={0} />
        </linearGradient>
        <linearGradient
          gradientUnits="userSpaceOnUse"
          id={paint1Id}
          x1={121}
          x2={120.799}
          y1={54}
          y2={106.875}
        >
          <stop stopColor="white" />
          <stop offset={1} stopColor="white" stopOpacity={0} />
        </linearGradient>
      </defs>
    </svg>
  )
}

export default HeroLiquidMetal

Similar components