PixelInputGroup

Visually joins multiple form controls into a single shell — strips inner borders/radii from children and adds segment dividers, so combos like country-code + phone read as one field.

  • Composes any form children (input, button, select) into a single joined shell.
  • size and surface props inherit the kit-wide design tokens.
  • Accessible: applies role="group" only when an aria-label/aria-labelledby is provided.
  • Dev-mode warning when a multi-child group is missing an accessible name.
  • Keeps the classes of each control alongside the join classes.
Status
stable
Category
forms
Since
v1.9.0

API

import { PixelInputGroup } from '@pxlkit/ui-kit';
PropTypeDefaultDescription
size'sm' | 'md' | 'lg''md'Height of the shell.
surface'pixel' | 'linear'—Surface override; defaults to the nearest provider.
aria-labelstring—Accessible name for the group. Strongly recommended since this primitive visually joins multiple form controls (e.g. country code + phone) — without an accessible name a screen reader user has no idea what the group represents. In development, a group of more than one child without a name logs a warning.
aria-labelledbystring—Id of the element that names the group, in place of aria-label.
children (required)React.ReactNode—The controls to join; each element is joined, text is dropped.

Also takes the native attributes and event handlers of <div> (HTMLAttributes<HTMLDivElement>).

ref points to <div>.

Accessibility

WCAG target: 2.1 AA

  • group

Sets role="group" only when given an accessible name (aria-label / aria-labelledby). Decorative addons inside should use aria-hidden; child controls keep their own labels. The shell clips its controls, so each shows keyboard focus inside its own edge.

Keyboard

KeyActionWhen
TabMoves focus through the joined controls in order.—

Usage

import { PixelInputGroup } from '@pxlkit/ui-kit';

export function Default() {
  return (
    <PixelInputGroup aria-label="Website URL">
      <input aria-label="Protocol" defaultValue="https://" className="bg-transparent px-2 outline-none" />
      <input aria-label="Domain name" defaultValue="pxlkit" className="bg-transparent px-2 outline-none" />
      <input aria-label="Top-level domain" defaultValue=".xyz" className="bg-transparent px-2 outline-none" />
    </PixelInputGroup>
  );
}

Examples

Default

import { PixelInputGroup } from '@pxlkit/ui-kit';

export function Default() {
  return (
    <PixelInputGroup aria-label="Website URL">
      <input aria-label="Protocol" defaultValue="https://" className="bg-transparent px-2 outline-none" />
      <input aria-label="Domain name" defaultValue="pxlkit" className="bg-transparent px-2 outline-none" />
      <input aria-label="Top-level domain" defaultValue=".xyz" className="bg-transparent px-2 outline-none" />
    </PixelInputGroup>
  );
}

Sizes

import { PixelInputGroup } from '@pxlkit/ui-kit';

export function Sizes() {
  return (
    <div className="flex flex-col gap-3">
      <PixelInputGroup size="sm" aria-label="Small group">
        <input aria-label="First segment" defaultValue="small" className="bg-transparent px-2 outline-none" />
        <input aria-label="Second segment" defaultValue="size" className="bg-transparent px-2 outline-none" />
      </PixelInputGroup>
      <PixelInputGroup size="md" aria-label="Medium group">
        <input aria-label="First segment" defaultValue="medium" className="bg-transparent px-2 outline-none" />
        <input aria-label="Second segment" defaultValue="size" className="bg-transparent px-2 outline-none" />
      </PixelInputGroup>
      <PixelInputGroup size="lg" aria-label="Large group">
        <input aria-label="First segment" defaultValue="large" className="bg-transparent px-2 outline-none" />
        <input aria-label="Second segment" defaultValue="size" className="bg-transparent px-2 outline-none" />
      </PixelInputGroup>
    </div>
  );
}

Surfaces

import { PixelInputGroup } from '@pxlkit/ui-kit';

export function Surfaces() {
  return (
    <div className="flex flex-col gap-3">
      <PixelInputGroup surface="pixel" aria-label="Pixel surface group">
        <input aria-label="First segment" defaultValue="pixel" className="bg-transparent px-2 outline-none" />
        <input aria-label="Second segment" defaultValue="surface" className="bg-transparent px-2 outline-none" />
      </PixelInputGroup>
      <PixelInputGroup surface="linear" aria-label="Linear surface group">
        <input aria-label="First segment" defaultValue="linear" className="bg-transparent px-2 outline-none" />
        <input aria-label="Second segment" defaultValue="surface" className="bg-transparent px-2 outline-none" />
      </PixelInputGroup>
    </div>
  );
}

Phone with Country Code

import { PixelInputGroup } from '@pxlkit/ui-kit';

export function PhoneWithCountryCode() {
  return (
    <PixelInputGroup aria-label="Phone number with country code">
      <input
        aria-label="Country code"
        defaultValue="+58"
        className="bg-transparent px-2 outline-none"
        style={{ maxWidth: '5rem' }}
      />
      <input
        aria-label="Phone number"
        placeholder="412 555 0123"
        className="bg-transparent px-2 outline-none"
      />
    </PixelInputGroup>
  );
}

Related