Skip to content
goodtabs UI
BrowseBrowse components

Addon input

An input with a text prefix or suffix inside the same bordered field.

npx shadcn@latest add https://ui.goodtabs.co/r/addon-input.json

With the @goodtabs namespace in components.json: npx shadcn@latest add @goodtabs/addon-input

https://

A protocol before a url field. The focus ring draws around the whole field.

With a suffix

.com

We check availability as you type.

A fixed top-level domain after the field, with a label and a hint.

Prefix and suffix

$per month

A currency symbol before and a unit after a numeric field in mono.

Usage

import { AddonInput } from "@/components/ui/addon-input"

<AddonInput prefix="https://" placeholder="goodtabs.co" />
<AddonInput suffix=".com" placeholder="yourcompany" />

Props

PropTypeDefaultDescription
prefixReactNodeText or node before the input, inside the same border.
suffixReactNodeText or node after the input, inside the same border.

Source

addon-input/addon-input.tsx
import * as React from "react";
import { Input } from "@/components/ui/input";
import { cn } from "@/lib/utils";

type AddonInputProps = Omit<React.ComponentProps<typeof Input>, "prefix"> & {
  /** Text or node before the input, inside the same border. For example "https://". */
  prefix?: React.ReactNode;
  /** Text or node after the input, inside the same border. For example ".com". */
  suffix?: React.ReactNode;
};

/**
 * An input with a text prefix and/or suffix in the same bordered field.
 * The addons sit on a muted band; focus and invalid states draw on the whole field.
 */
export function AddonInput({ prefix, suffix, className, disabled, ...props }: AddonInputProps) {
  return (
    <div
      data-slot="addon-input"
      className={cn(
        "flex h-8 w-full min-w-0 items-stretch overflow-hidden rounded-md border border-input bg-transparent transition-colors focus-within:border-ring focus-within:ring-3 focus-within:ring-ring/50 has-disabled:pointer-events-none has-disabled:cursor-not-allowed has-disabled:opacity-50 has-aria-invalid:border-destructive has-aria-invalid:ring-3 has-aria-invalid:ring-destructive/20 dark:bg-input/30 dark:has-aria-invalid:border-destructive/50 dark:has-aria-invalid:ring-destructive/40",
        className,
      )}
    >
      {prefix !== undefined && prefix !== null && (
        <span
          data-slot="addon-input-prefix"
          className="flex shrink-0 items-center border-r bg-muted px-2.5 text-sm whitespace-nowrap text-foreground/70 select-none"
        >
          {prefix}
        </span>
      )}
      <Input
        disabled={disabled}
        className="h-full min-w-0 flex-1 rounded-none border-0 bg-transparent shadow-none focus-visible:border-transparent focus-visible:ring-0 disabled:bg-transparent disabled:opacity-100 aria-invalid:ring-0 dark:bg-transparent dark:disabled:bg-transparent"
        {...props}
      />
      {suffix !== undefined && suffix !== null && (
        <span
          data-slot="addon-input-suffix"
          className="flex shrink-0 items-center border-l bg-muted px-2.5 text-sm whitespace-nowrap text-foreground/70 select-none"
        >
          {suffix}
        </span>
      )}
    </div>
  );
}

For AI agents

Hand your agent this page, the markdown at /components/addon-input/prompt.md, or the Copy AI prompt button above.

When to use

  • A url, handle or domain where part of the value is fixed and should not be typed
  • Money, percentages and quantities that need a symbol or unit next to the number
  • Anywhere a hint about the format belongs inside the field, not in a placeholder

When not to use

  • For icons or buttons inside a field: use a plain Input with an absolutely placed control
  • When the prefix or suffix changes with the value, use a select or a combobox next to the field
  • For search, use the search input

Agent notes

  • Server component. It extends shadcn's Input, so type, disabled, aria-invalid and every input prop pass through
  • className goes on the wrapper, so widths and font classes apply to the whole field; the Input stretches inside it
  • The addons are not part of the value: prepend or append them yourself before saving
  • Pass aria-label or wire a Label with htmlFor to the id, as with any input