# Border Beam Input

> React input for shadcn/ui built on Base UI with an animated border-beam glow along the bottom edge or the full border.

Source: https://www.cult-ui.com/docs/components/border-beam-input

## Example

```tsx title="border-beam-input-demo.tsx"
"use client"

import { useId } from "react"
import { Search } from "lucide-react"

import {
  BorderBeamInput,
  BorderBeamInputGroup,
  BorderBeamInputGroupAddon,
  BorderBeamInputGroupInput,
} from "@/components/ui/border-beam-input"

export default function BorderBeamInputDemo() {
  const searchId = useId()

  return (
    <main className="flex w-full flex-col items-center justify-center">
      <div className="flex w-full max-w-3xl flex-col gap-10">
        <header className="space-y-2 text-center">
          <h2 className="font-semibold text-foreground text-xl tracking-tight">
            Border beam input
          </h2>
          <p className="text-pretty text-muted-foreground text-sm">
            <span className="text-foreground/90">BorderBeamInput</span> and{" "}
            <span className="text-foreground/90">BorderBeamInputGroup</span>{" "}
            default to{" "}
            <span className="text-foreground/90">
              beamSize=&quot;line&quot;
            </span>{" "}
            (bottom-travel glow). Use{" "}
            <span className="text-foreground/90">pill</span> on the group for a
            rounded search bar. Addons match{" "}
            <code className="rounded bg-muted px-1.5 py-0.5 font-mono text-foreground text-xs">
              InputGroup
            </code>{" "}
            composition.
          </p>
        </header>

        <section className="space-y-3">
          <h3 className="font-semibold text-foreground text-lg tracking-tight">
            Single input & pill group
          </h3>
          <div className="flex max-w-md flex-col gap-4">
            <BorderBeamInput placeholder="Single input, line beam…" />
            <BorderBeamInputGroup className="max-w-md" pill>
              <label className="sr-only" htmlFor={searchId}>
                Search
              </label>
              <BorderBeamInputGroupAddon aria-hidden>
                <Search className="size-4 text-muted-foreground" />
              </BorderBeamInputGroupAddon>
              <BorderBeamInputGroupInput
                id={searchId}
                placeholder="Search…"
                type="search"
              />
            </BorderBeamInputGroup>
          </div>
        </section>

        <section className="space-y-3">
          <h3 className="font-semibold text-foreground text-lg tracking-tight">
            Line beam (InputGroup)
          </h3>
          <p className="text-pretty text-muted-foreground text-sm">
            Same pattern as a compact toolbar search — icon + control inside{" "}
            <span className="text-foreground/90">BorderBeamInputGroup</span>.
          </p>
          <div className="pb-8">
            <BorderBeamInputGroup className="max-w-md" theme="auto">
              <label className="sr-only" htmlFor="border-beam-line-input-demo">
                Search
              </label>
              <BorderBeamInputGroupAddon aria-hidden>
                <Search className="size-4 text-muted-foreground" />
              </BorderBeamInputGroupAddon>
              <BorderBeamInputGroupInput
                id="border-beam-line-input-demo"
                placeholder="line beam (InputGroup)…"
                type="search"
              />
            </BorderBeamInputGroup>
          </div>
        </section>
      </div>
    </main>
  )
}
```

Border Beam Input is a React input component for shadcn/ui built on Base UI and the border-beam package. Use it for AI prompt fields, search bars, or newsletter signup forms.

## Installation

### CLI

```bash
npx shadcn@latest add @cult-ui/border-beam-input
```

### Manual

**Install the required dependencies:**

```bash
npm install border-beam @base-ui/react
```

**Add the shadcn `input-group` component if you do not already have it.**

**Copy and paste the following code into your project.**

```tsx title="border-beam-input.tsx"
"use client"

/**
 * `border-beam` around `InputPrimitive` or `InputGroup` — default `beamSize="line"` bottom-travel glow.
 * `className` is merged onto both the beam wrapper and the inner control so width constraints match.
 * The wrapper is a column flex shell (`borderBeamFieldShellClass`) so the beam box stretches to the
 * same width as the control in flex layouts. Note: `beamSize="line"` draws a traveling spot along the
 * bottom (not a static full-width bar); use `beamSize="md"` for a full border glow.
 *
 * `BorderBeamInput` uses `@base-ui/react/input` directly (not `components/ui/input`) so styling is
 * controlled here via `borderBeamInputPrimitiveClass` and `className`.
 */
import type { ComponentProps, CSSProperties } from "react"
import { forwardRef } from "react"
import { Input as InputPrimitive } from "@base-ui/react/input"
import {
  BorderBeam,
  type BorderBeamProps,
  type BorderBeamSize,
} from "border-beam"

import { cn } from "@/lib/utils"
import {
  InputGroup,
  InputGroupAddon,
  InputGroupButton,
  InputGroupInput,
  InputGroupText,
  InputGroupTextarea,
} from "@/components/ui/input-group"

/** Mirrors `components/ui/input` shell — adjust here for border-beam-specific fields. */
const borderBeamInputPrimitiveClass =
  "box-border h-7 w-full min-w-0 self-stretch rounded-md border border-input bg-input/20 px-2 py-0.5 text-sm outline-none transition-colors file:inline-flex file:h-6 file:border-0 file:bg-transparent file:font-medium file:text-foreground file:text-xs/relaxed placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-[2px] focus-visible:ring-ring/30 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-[2px] aria-invalid:ring-destructive/20 md:text-xs/relaxed dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40"

/**
 * Beam layers use `inset:0` on this box — it must match the control width. `flex` + `stretch`
 * avoids shrink-to-content in flex parents; `max-w-full` keeps `className` max-width in sync.
 */
const borderBeamFieldShellClass =
  "flex w-full min-w-0 max-w-full flex-col items-stretch overflow-visible!"

type BeamShellProps = Pick<
  BorderBeamProps,
  | "colorVariant"
  | "theme"
  | "staticColors"
  | "duration"
  | "active"
  | "borderRadius"
  | "brightness"
  | "saturation"
  | "hueRange"
  | "strength"
  | "onActivate"
  | "onDeactivate"
> & {
  beamSize?: BorderBeamSize
  borderBeamClassName?: string
  borderBeamStyle?: CSSProperties
}

export type BorderBeamInputProps = ComponentProps<typeof InputPrimitive> &
  BeamShellProps & {
    /** Fully rounded input (matches pill search bars). */
    pill?: boolean
  }

export const BorderBeamInput = forwardRef<HTMLDivElement, BorderBeamInputProps>(
  function BorderBeamInput(
    {
      beamSize = "line",
      borderBeamClassName,
      borderBeamStyle,
      theme = "auto",
      colorVariant,
      staticColors,
      duration,
      active,
      borderRadius,
      brightness,
      saturation,
      hueRange,
      strength,
      onActivate,
      onDeactivate,
      pill,
      className,
      type,
      ...inputProps
    },
    ref
  ) {
    return (
      <BorderBeam
        active={active}
        borderRadius={borderRadius}
        brightness={brightness}
        className={cn(
          borderBeamFieldShellClass,
          borderBeamClassName,
          className
        )}
        colorVariant={colorVariant}
        duration={duration}
        hueRange={hueRange}
        onActivate={onActivate}
        onDeactivate={onDeactivate}
        ref={ref}
        saturation={saturation}
        size={beamSize}
        staticColors={staticColors}
        strength={strength}
        style={borderBeamStyle}
        theme={theme}
      >
        <InputPrimitive
          className={cn(
            borderBeamInputPrimitiveClass,
            pill && "h-9 rounded-full",
            className
          )}
          data-slot="input"
          type={type}
          {...inputProps}
        />
      </BorderBeam>
    )
  }
)

BorderBeamInput.displayName = "BorderBeamInput"

export type BorderBeamInputGroupProps = ComponentProps<typeof InputGroup> &
  BeamShellProps & {
    /** Full pill shape (`rounded-full`, taller shell). */
    pill?: boolean
  }

export const BorderBeamInputGroup = forwardRef<
  HTMLDivElement,
  BorderBeamInputGroupProps
>(function BorderBeamInputGroup(
  {
    beamSize = "line",
    borderBeamClassName,
    borderBeamStyle,
    theme = "auto",
    colorVariant,
    staticColors,
    duration,
    active,
    borderRadius,
    brightness,
    saturation,
    hueRange,
    strength,
    onActivate,
    onDeactivate,
    pill,
    className,
    ...groupProps
  },
  ref
) {
  return (
    <BorderBeam
      active={active}
      borderRadius={borderRadius}
      brightness={brightness}
      className={cn(borderBeamFieldShellClass, borderBeamClassName, className)}
      colorVariant={colorVariant}
      duration={duration}
      hueRange={hueRange}
      onActivate={onActivate}
      onDeactivate={onDeactivate}
      ref={ref}
      saturation={saturation}
      size={beamSize}
      staticColors={staticColors}
      strength={strength}
      style={borderBeamStyle}
      theme={theme}
    >
      <InputGroup
        className={cn(
          "min-w-0 self-stretch",
          pill && "h-9 rounded-full has-[textarea]:rounded-md",
          className
        )}
        {...groupProps}
      />
    </BorderBeam>
  )
})

BorderBeamInputGroup.displayName = "BorderBeamInputGroup"

export function BorderBeamInputGroupAddon(
  props: ComponentProps<typeof InputGroupAddon>
) {
  return <InputGroupAddon {...props} />
}
BorderBeamInputGroupAddon.displayName = "BorderBeamInputGroupAddon"

export function BorderBeamInputGroupButton(
  props: ComponentProps<typeof InputGroupButton>
) {
  return <InputGroupButton {...props} />
}
BorderBeamInputGroupButton.displayName = "BorderBeamInputGroupButton"

export function BorderBeamInputGroupInput(
  props: ComponentProps<typeof InputGroupInput>
) {
  return <InputGroupInput {...props} />
}
BorderBeamInputGroupInput.displayName = "BorderBeamInputGroupInput"

export function BorderBeamInputGroupText(
  props: ComponentProps<typeof InputGroupText>
) {
  return <InputGroupText {...props} />
}
BorderBeamInputGroupText.displayName = "BorderBeamInputGroupText"

export function BorderBeamInputGroupTextarea(
  props: ComponentProps<typeof InputGroupTextarea>
) {
  return <InputGroupTextarea {...props} />
}
BorderBeamInputGroupTextarea.displayName = "BorderBeamInputGroupTextarea"
```

**Update the import paths to match your project setup.**
