InputGroup 输入框组
@lark-ui/core
import { useRef, useState } from "react";
import { InputGroup, Loader } from "@lark-ui/core";
import { CheckCircleIcon } from "@phosphor-icons/react";

export function InputGroupDemo() {
  const [status, setStatus] = useState<"idle" | "loading" | "success">(
    "success",
  );
  const [value, setValue] = useState("lark");
  const timerRef = useRef<ReturnType<typeof setTimeout>>(null);

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const next = e.target.value;

    setValue(next);

    if (timerRef.current) clearTimeout(timerRef.current);

    if (next.length > 0) {
      setStatus("loading");

      timerRef.current = setTimeout(() => setStatus("success"), 1500);
    } else {
      setStatus("idle");
    }
  };

  return (
    <div className="w-full max-w-2xs">
      <InputGroup>
        <InputGroup.Input
          maxLength={20}
          onChange={handleChange}
          value={value}
        />
        <InputGroup.Suffix>.workers.dev</InputGroup.Suffix>
        {status !== "idle" && (
          <InputGroup.Addon align="end">
            {status === "loading" ? (
              <Loader />
            ) : (
              <CheckCircleIcon weight="duotone" className="text-lark-success" />
            )}
          </InputGroup.Addon>
        )}
      </InputGroup>
    </div>
  );
}

安装

统一导出

import { InputGroup } from "@lark-ui/core";

按需导出

import { InputGroup } from "@lark-ui/core/components/input-group";

用法

使用内置 Field(推荐)

为 InputGroup 传入 label 属性可启用内置的 Field 包装器,获得标签、描述和错误提示支持。

import { InputGroup } from "@lark-ui/core";
import { MagnifyingGlassIcon } from "@phosphor-icons/react";

export default function Example() {
  return (
    <InputGroup label="Search" description="Find pages, components, and more">
      <InputGroup.Addon>
        <MagnifyingGlassIcon />
      </InputGroup.Addon>
      <InputGroup.Input placeholder="Search..." />
    </InputGroup>
  );
}

裸用 InputGroup(自定义布局)

在自定义表单布局中,请不要使用 label,直接使用 InputGroup。出于无障碍考虑,必须在 InputGroup.Input 上提供 aria-label。

import { InputGroup } from "@lark-ui/core";
import { MagnifyingGlassIcon } from "@phosphor-icons/react";

export default function Example() {
  return (
    <InputGroup>
      <InputGroup.Addon>
        <MagnifyingGlassIcon />
      </InputGroup.Addon>
      <InputGroup.Input placeholder="Search..." aria-label="Search" />
    </InputGroup>
  );
}

示例

图标

使用 Addon 在输入框起始处放置图标,作为视觉标识。

import { InputGroup } from "@lark-ui/core";
import { LinkIcon } from "@phosphor-icons/react";

export function InputGroupIconsDemo() {
  return (
    <InputGroup className="w-full max-w-3xs">
      <InputGroup.Addon>
        <LinkIcon />
      </InputGroup.Addon>
      <InputGroup.Input placeholder="粘贴链接…" aria-label="链接" />
    </InputGroup>
  );
}

文本

使用 Addon 在输入框旁放置文本前缀或后缀。

import { InputGroup } from "@lark-ui/core";

export function InputGroupTextDemo() {
  return (
    <div className="flex flex-col gap-4">
      <InputGroup className="w-full max-w-3xs">
        <InputGroup.Addon>@</InputGroup.Addon>
        <InputGroup.Input placeholder="用户名" aria-label="用户名" />
      </InputGroup>

      <InputGroup className="w-full max-w-3xs">
        <InputGroup.Input placeholder="邮箱" aria-label="邮箱" />
        <InputGroup.Addon align="end">@example.com</InputGroup.Addon>
      </InputGroup>

      <InputGroup className="w-full max-w-3xs">
        <InputGroup.Addon>/api/</InputGroup.Addon>
        <InputGroup.Input placeholder="端点" aria-label="API 路径" />
        <InputGroup.Addon align="end">.json</InputGroup.Addon>
      </InputGroup>
    </div>
  );
}

按钮

将 InputGroup.Button 放在 Addon 内,用于直接操作输入值的动作,例如显示/隐藏或清空。

import { useState } from "react";
import { InputGroup } from "@lark-ui/core";
import { MagnifyingGlassIcon, EyeIcon, EyeSlashIcon, XIcon } from "@phosphor-icons/react";

export function InputGroupButtonsDemo() {
  const [show, setShow] = useState(false);
  const [searchValue, setSearchValue] = useState("search");

  return (
    <div className="flex flex-col gap-4">
      <InputGroup className="w-full max-w-3xs">
        <InputGroup.Input
          type={show ? "text" : "password"}
          defaultValue="password"
          aria-label="密码"
        />
        <InputGroup.Addon align="end">
          <InputGroup.Button
            shape="square"
            className="text-lark-subtle"
            icon={show ? EyeSlashIcon : EyeIcon}
            aria-label={show ? "隐藏密码" : "显示密码"}
            onClick={() => setShow(!show)}
          />
        </InputGroup.Addon>
      </InputGroup>

      <InputGroup className="w-full max-w-3xs">
        <InputGroup.Addon>
          <MagnifyingGlassIcon />
        </InputGroup.Addon>
        <InputGroup.Input
          value={searchValue}
          placeholder="搜索"
          aria-label="搜索"
          onChange={(e) => setSearchValue(e.target.value)}
        />
        {searchValue && (
          <InputGroup.Addon align="end" className="pr-1">
            <InputGroup.Button
              shape="square"
              icon={XIcon}
              aria-label="清除搜索"
              onClick={() => setSearchValue("")}
            />
          </InputGroup.Addon>
        )}
        <InputGroup.Button variant="secondary" onClick={() => {}}>
          搜索
        </InputGroup.Button>
      </InputGroup>
    </div>
  );
}

带工具提示的按钮

为 InputGroup.Button 传入 tooltip 属性即可在悬停时显示工具提示。未显式提供 aria-label 时,按钮会从字符串类型的 tooltip 值中生成它。

import { InputGroup } from "@lark-ui/core";
import { MagnifyingGlassIcon, QuestionIcon } from "@phosphor-icons/react";

export function InputGroupTooltipButtonDemo() {
  return (
    <InputGroup className="w-full max-w-2xs">
      <InputGroup.Addon>
        <MagnifyingGlassIcon />
      </InputGroup.Addon>
      <InputGroup.Input placeholder="使用查询语言搜索…" aria-label="搜索" />
      <InputGroup.Addon align="end">
        <InputGroup.Button
          shape="square"
          className="text-lark-subtle"
          icon={QuestionIcon}
          aria-label="查询语言帮助"
          tooltip="查询语言帮助"
          onClick={() => {}}
        />
      </InputGroup.Addon>
    </InputGroup>
  );
}

快捷键

在末尾的 Addon 内放置键盘快捷键提示。

import { InputGroup } from "@lark-ui/core";
import { MagnifyingGlassIcon } from "@phosphor-icons/react";

export function InputGroupKbdDemo() {
  return (
    <InputGroup className="w-full max-w-3xs">
      <InputGroup.Addon>
        <MagnifyingGlassIcon />
      </InputGroup.Addon>
      <InputGroup.Input placeholder="搜索…" aria-label="搜索" />
      <InputGroup.Addon align="end">
        <kbd className="border-none! bg-none!">⌘K</kbd>
      </InputGroup.Addon>
    </InputGroup>
  );
}

加载中

在校验输入值期间,可在末尾的 Addon 内放置 Loader 作为状态指示器。

import { InputGroup, Loader } from "@lark-ui/core";

export function InputGroupLoadingDemo() {
  return (
    <InputGroup className="w-full max-w-3xs">
      <InputGroup.Input defaultValue="lark" aria-label="lark" />
      <InputGroup.Addon align="end">
        <Loader />
      </InputGroup.Addon>
    </InputGroup>
  );
}

行内后缀

Suffix 会渲染与输入内容自然衔接的文本——适合 .workers.dev 这类域名输入。可搭配状态图标 Addon 来展示校验状态。

.workers.dev
.workers.dev
该子域名不可用
import { InputGroup } from "@lark-ui/core";
import { CheckCircleIcon, XCircleIcon } from "@phosphor-icons/react";

export function InputGroupSuffixDemo() {
  return (
    <div className="flex w-full max-w-2xs flex-col gap-4">
      <InputGroup label="子域名">
        <InputGroup.Input
          aria-label="子域名"
          defaultValue="lark"
          maxLength={20}
        />
        <InputGroup.Suffix>.workers.dev</InputGroup.Suffix>
        <InputGroup.Addon align="end">
          <CheckCircleIcon weight="duotone" className="text-lark-success" />
        </InputGroup.Addon>
      </InputGroup>

      <InputGroup
        label="子域名"
        error={{ message: "该子域名不可用", match: true }}
      >
        <InputGroup.Input
          aria-label="子域名"
          defaultValue="lark"
          maxLength={20}
        />
        <InputGroup.Suffix>.workers.dev</InputGroup.Suffix>
        <InputGroup.Addon align="end">
          <XCircleIcon weight="duotone" className="text-lark-danger" />
        </InputGroup.Addon>
      </InputGroup>
    </div>
  );
}

尺寸

提供四种尺寸:xs、sm、base(默认)和 lg。尺寸作用于整个输入框组。

import { InputGroup } from "@lark-ui/core";
import { MagnifyingGlassIcon, QuestionIcon } from "@phosphor-icons/react";

export function InputGroupSizesDemo() {
  return (
    <div className="flex w-full max-w-3xs flex-col gap-4">
      <InputGroup size="xs" label="超小">
        <InputGroup.Addon>
          <MagnifyingGlassIcon />
        </InputGroup.Addon>
        <InputGroup.Input placeholder="超小尺寸输入框" />
        <InputGroup.Addon align="end">
          <InputGroup.Button
            className="text-lark-subtle"
            icon={QuestionIcon}
            shape="square"
            aria-label="帮助"
          />
        </InputGroup.Addon>
      </InputGroup>

      <InputGroup size="sm" label="小">
        <InputGroup.Addon>
          <MagnifyingGlassIcon />
        </InputGroup.Addon>
        <InputGroup.Input placeholder="小尺寸输入框" />
        <InputGroup.Addon align="end">
          <InputGroup.Button
            className="text-lark-subtle"
            icon={QuestionIcon}
            shape="square"
            aria-label="帮助"
          />
        </InputGroup.Addon>
      </InputGroup>

      <InputGroup label="基础(默认)">
        <InputGroup.Addon>
          <MagnifyingGlassIcon />
        </InputGroup.Addon>
        <InputGroup.Input placeholder="基础尺寸输入框" />
        <InputGroup.Addon align="end">
          <InputGroup.Button
            className="text-lark-subtle"
            icon={QuestionIcon}
            shape="square"
            aria-label="帮助"
          />
        </InputGroup.Addon>
      </InputGroup>

      <InputGroup size="lg" label="大">
        <InputGroup.Addon>
          <MagnifyingGlassIcon />
        </InputGroup.Addon>
        <InputGroup.Input placeholder="大尺寸输入框" />
        <InputGroup.Addon align="end">
          <InputGroup.Button
            className="text-lark-subtle"
            icon={QuestionIcon}
            shape="square"
            aria-label="帮助"
          />
        </InputGroup.Addon>
      </InputGroup>
    </div>
  );
}

状态

支持多种输入状态,包括错误、禁用和带描述。将 label、error 和 description 属性直接传给 InputGroup 即可。

@example.com
请输入有效的邮箱地址
$

至少需要 8 个字符

import { useState } from "react";
import { InputGroup } from "@lark-ui/core";
import { MagnifyingGlassIcon, EyeIcon, EyeSlashIcon } from "@phosphor-icons/react";

export function InputGroupStatesDemo() {
  const [show, setShow] = useState(false);

  return (
    <div className="flex w-full max-w-3xs flex-col gap-4">
      <InputGroup
        label="错误状态"
        error={{ message: "请输入有效的邮箱地址", match: true }}
      >
        <InputGroup.Input type="email" defaultValue="invalid-email" />
        <InputGroup.Addon align="end">@example.com</InputGroup.Addon>
      </InputGroup>

      <InputGroup label="禁用" disabled>
        <InputGroup.Addon>
          <MagnifyingGlassIcon />
        </InputGroup.Addon>
        <InputGroup.Input placeholder="搜索…" />
      </InputGroup>

      <InputGroup label="可选字段" required={false}>
        <InputGroup.Addon>$</InputGroup.Addon>
        <InputGroup.Input placeholder="0.00" />
      </InputGroup>

      <InputGroup
        label="带描述"
        description="至少需要 8 个字符"
        labelTooltip="你的密码会被安全存储"
      >
        <InputGroup.Input
          type={show ? "text" : "password"}
          placeholder="密码"
        />
        <InputGroup.Addon align="end">
          <InputGroup.Button
            shape="square"
            className="text-lark-subtle"
            icon={show ? EyeSlashIcon : EyeIcon}
            aria-label={show ? "隐藏密码" : "显示密码"}
            onClick={() => setShow(!show)}
          />
        </InputGroup.Addon>
      </InputGroup>
    </div>
  );
}

API 参考

InputGroup

为所有子组件提供上下文的根容器。接受 Field 属性(label、description、error),在提供 label 时会用 Field 包裹内容。

属性类型默认值说明
labelReactNode-标签内容——可以是字符串或任意 React 节点。
descriptionReactNode-显示在控件下方的辅助说明文字(存在 `error` 时隐藏)。
errorobject-带错误信息和浏览器 `ValidityState` 匹配键的校验错误。
requiredboolean-显式设为 `false` 时,在标签后显示灰色的“(optional)”文字。设为 `true` 或 `undefined` 时不显示任何标识。
labelTooltipReactNode-通过信息图标显示在标签旁的工具提示内容。
childrenReactNode--
classNamestring--
idstring--
langstring--
titlestring--
size"xs" | "sm" | "base" | "lg""base"-
disabledboolean--

InputGroup.Input

文本输入元素。从 InputGroup 上下文继承 size、disabled 和 error。接受所有标准 input 属性,但与 Field 相关的属性除外(由父组件处理)。

属性类型默认值

该组件没有专有属性,支持标准 HTML 属性。

InputGroup.Addon

位于输入框起始或末尾的容器,用于放置图标、文本或紧凑按钮。

属性类型默认值说明
align"start" | "end""start"相对于输入框的位置。
classNamestring-额外的 CSS 类名。

InputGroup.Button

用于切换、复制、帮助等次要操作的按钮。渲染在 Addon 内部。传入 tooltip 属性即可在悬停时显示工具提示。

属性类型默认值说明
tooltipReactNode-提供时用 Tooltip 包裹按钮。若传入字符串值,会自动设置 aria-label。
tooltipSide"top" | "right" | "bottom" | "left""bottom"工具提示弹出层偏好的显示位置。
variant"primary" | "secondary" | "ghost" | "destructive" | "secondary-destructive" | "outline""ghost"按钮的视觉样式。默认为 ghost。
size"xs" | "sm" | "base" | "lg""sm"按钮尺寸。

InputGroup.Suffix

与输入内容自然衔接的行内文本(例如 .workers.dev)。输入框宽度会随着用户输入自动调整。

属性类型默认值说明
classNamestring-额外的 CSS 类名。

校验错误类型

将 error 用作对象时,match 属性对应 HTML5 ValidityState 的取值:

匹配值说明
valueMissing必填字段为空
typeMismatch值与类型不匹配(例如邮箱格式无效)
patternMismatch值与 pattern 属性不匹配
tooShort值短于 minLength
tooLong值长于 maxLength
rangeUnderflow值小于 min
rangeOverflow值大于 max
true始终显示错误(用于服务端校验)

无障碍

标签要求

InputGroup 需要通过以下方式之一获得无障碍名称:

  • InputGroup 上的 label 属性(渲染可见标签,并支持内置 Field)

  • 没有可见标签时,在 InputGroup.Input 上使用 aria-label

  • 自定义标签关联时,在 InputGroup.Input 上使用 aria-labelledby

缺少无障碍名称会在开发模式下触发控制台警告。

分组角色

InputGroup 会自动以 role="group" 渲染,在语义上将输入框与其附加内容关联起来,便于辅助技术识别。