Skip to content
Lenso UI

InputGroup 输入框组

将相关输入控件与前后缀元素组合,以增强表单字段

用法

import { InputGroup } from '@lenso/ui';

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";
import { Envelope } from "@gravity-ui/icons";import { InputGroup, Label, TextField } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";
// Adapted from HeroUI v3.2.6, Apache-2.0.const styles = stylex.create({  field: { width: "100%", maxWidth: 280 },  icon: { width: 16, height: 16, color: "var(--muted)" },});
export function Default() {  return (    <TextField xstyle={styles.field} name="email">      <Label>Email address</Label>      <InputGroup>        <InputGroup.Prefix>          <Envelope aria-hidden="true" {...stylex.props(styles.icon)} />        </InputGroup.Prefix>        <InputGroup.Input xstyle={styles.field} placeholder="[email protected]" />      </InputGroup>    </TextField>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

组件结构

import {InputGroup, TextField, Label} from '@lenso/ui';
export default () => (  <TextField>    <Label />    <InputGroup>      <InputGroup.Prefix />      <InputGroup.Input /> {/* Or use InputGroup.TextArea for multiline input */}      <InputGroup.Suffix />    </InputGroup>  </TextField>)

InputGroup 使用可选的前缀与后缀包裹输入框,形成视觉上统一的组合。通常放在 TextField 内,用于在输入前后添加图标、文字、按钮等元素。单行输入请使用 InputGroup.Input,多行输入请使用 InputGroup.TextArea。

示例

变体

InputGroup 组件支持两种视觉变体:

  • primary(默认)- 标准样式带阴影,适用于大多数场景
  • secondary - 低强调变体无阴影,适用于 Surface 组件内

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";// Adapted from HeroUI v3.2.6, Apache-2.0.import { Envelope } from "@gravity-ui/icons";import { InputGroup, Label, TextField } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({  stack: { display: "flex", flexDirection: "column", gap: 16 },  field: { width: 280 },  icon: { width: 16, height: 16, color: "var(--muted)" },});export function Variants() {  return (    <div {...stylex.props(styles.stack)}>      <TextField xstyle={styles.field} name="primary">        <Label>Primary variant</Label>        <InputGroup variant="primary">          <InputGroup.Prefix>            <Envelope aria-hidden="true" {...stylex.props(styles.icon)} />          </InputGroup.Prefix>          <InputGroup.Input placeholder="[email protected]" />        </InputGroup>      </TextField>      <TextField xstyle={styles.field} name="secondary">        <Label>Secondary variant</Label>        <InputGroup variant="secondary">          <InputGroup.Prefix>            <Envelope aria-hidden="true" {...stylex.props(styles.icon)} />          </InputGroup.Prefix>          <InputGroup.Input placeholder="[email protected]" />        </InputGroup>      </TextField>    </div>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

表面样式

在 Surface 内使用时,请使用 variant="secondary" 以应用适合 Surface 背景的低强调变体。

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";// Adapted from HeroUI v3.2.6, Apache-2.0.import { Envelope } from "@gravity-ui/icons";import { Description, InputGroup, Label, Surface, TextField } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({  surface: { borderRadius: 16, padding: 24 },  field: { width: "100%", maxWidth: 280 },  icon: { width: 16, height: 16, color: "var(--muted)" },});export function OnSurface() {  return (    <Surface xstyle={styles.surface}>      <TextField xstyle={styles.field} name="email">        <Label>Email address</Label>        <InputGroup variant="secondary">          <InputGroup.Prefix>            <Envelope aria-hidden="true" {...stylex.props(styles.icon)} />          </InputGroup.Prefix>          <InputGroup.Input xstyle={styles.field} placeholder="[email protected]" />        </InputGroup>        <Description>We'll never share this with anyone else</Description>      </TextField>    </Surface>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

后缀加载状态

在后缀显示加载 spinner 以表示正在处理。

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";// Adapted from HeroUI v3.2.6, Apache-2.0.import { InputGroup, Spinner, TextField } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({  field: { width: "100%", maxWidth: 280 },  spinner: { width: 16, height: 16 },});export function WithLoadingSuffix() {  return (    <TextField xstyle={styles.field} name="status">      <InputGroup>        <InputGroup.Input xstyle={styles.field} defaultValue="Sending..." />        <InputGroup.Suffix>          <Spinner xstyle={styles.spinner} />        </InputGroup.Suffix>      </InputGroup>    </TextField>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

必填字段

InputGroup 会遵循父级 TextField 的必填状态。

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";// Adapted from HeroUI v3.2.6, Apache-2.0.import { Envelope } from "@gravity-ui/icons";import { Description, InputGroup, Label, TextField } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({  stack: { display: "flex", flexDirection: "column", gap: 16 },  field: { width: "100%", maxWidth: 280 },  price: { width: "100%", maxWidth: 200 },  icon: { width: 16, height: 16, color: "var(--muted)" },});export function Required() {  return (    <div {...stylex.props(styles.stack)}>      <TextField xstyle={styles.field} name="email">        <Label required>Email address</Label>        <InputGroup>          <InputGroup.Prefix>            <Envelope aria-hidden="true" {...stylex.props(styles.icon)} />          </InputGroup.Prefix>          <InputGroup.Input required xstyle={styles.field} placeholder="[email protected]" />        </InputGroup>      </TextField>      <TextField xstyle={styles.field} name="price">        <Label required>Set a price</Label>        <InputGroup>          <InputGroup.Prefix>$</InputGroup.Prefix>          <InputGroup.Input required xstyle={styles.price} placeholder="0" type="number" />          <InputGroup.Suffix>USD</InputGroup.Suffix>        </InputGroup>        <Description>What customers would pay</Description>      </TextField>    </div>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

禁用状态

InputGroup 会遵循父级 TextField 的禁用状态。

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";// Adapted from HeroUI v3.2.6, Apache-2.0.import { Envelope } from "@gravity-ui/icons";import { InputGroup, Label, TextField } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({  stack: { display: "flex", flexDirection: "column", gap: 16 },  field: { width: "100%", maxWidth: 280 },  price: { width: "100%", maxWidth: 200 },  icon: { width: 16, height: 16, color: "var(--muted)" },});export function Disabled() {  return (    <div {...stylex.props(styles.stack)}>      <TextField disabled xstyle={styles.field} name="email">        <Label>Email address</Label>        <InputGroup>          <InputGroup.Prefix>            <Envelope aria-hidden="true" {...stylex.props(styles.icon)} />          </InputGroup.Prefix>          <InputGroup.Input xstyle={styles.field} defaultValue="[email protected]" />        </InputGroup>      </TextField>      <TextField disabled xstyle={styles.field} name="price">        <Label>Set a price</Label>        <InputGroup>          <InputGroup.Prefix>$</InputGroup.Prefix>          <InputGroup.Input xstyle={styles.price} defaultValue="10" type="number" />          <InputGroup.Suffix>USD</InputGroup.Suffix>        </InputGroup>      </TextField>    </div>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

宽度充满

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";// Adapted from HeroUI v3.2.6, Apache-2.0.import { Envelope, Eye } from "@gravity-ui/icons";import { InputGroup, Label, TextField } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({  stack: { width: 400, display: "flex", flexDirection: "column", gap: 16 },  icon: { width: 16, height: 16, color: "var(--muted)" },});export function FullWidth() {  return (    <div {...stylex.props(styles.stack)}>      <TextField fullWidth name="email">        <Label>Email address</Label>        <InputGroup fullWidth>          <InputGroup.Prefix>            <Envelope aria-hidden="true" {...stylex.props(styles.icon)} />          </InputGroup.Prefix>          <InputGroup.Input placeholder="[email protected]" />        </InputGroup>      </TextField>      <TextField fullWidth name="password">        <Label>Password</Label>        <InputGroup fullWidth>          <InputGroup.Input placeholder="Enter password" type="password" />          <InputGroup.Suffix>            <Eye aria-hidden="true" {...stylex.props(styles.icon)} />          </InputGroup.Suffix>        </InputGroup>      </TextField>    </div>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

文字前缀

使用文字作为前缀,例如货币符号或协议前缀。

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";// Adapted from HeroUI v3.2.6, Apache-2.0.import { InputGroup, Label, TextField } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({ field: { width: "100%", maxWidth: 280 } });export function WithTextPrefix() {  return (    <TextField xstyle={styles.field} name="website">      <Label>Website</Label>      <InputGroup>        <InputGroup.Prefix>https://</InputGroup.Prefix>        <InputGroup.Input xstyle={styles.field} defaultValue="heroui.com" />      </InputGroup>    </TextField>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

文字后缀

使用文字作为后缀,例如域名后缀或单位。

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";// Adapted from HeroUI v3.2.6, Apache-2.0.import { InputGroup, Label, TextField } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({ field: { width: "100%", maxWidth: 280 } });export function WithTextSuffix() {  return (    <TextField xstyle={styles.field} name="website">      <Label>Website</Label>      <InputGroup>        <InputGroup.Input xstyle={styles.field} defaultValue="heroui" />        <InputGroup.Suffix>.com</InputGroup.Suffix>      </InputGroup>    </TextField>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

图标前缀与文字后缀

组合图标前缀与文字后缀。

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";// Adapted from HeroUI v3.2.6, Apache-2.0.import { Globe } from "@gravity-ui/icons";import { InputGroup, Label, TextField } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({  field: { width: "100%", maxWidth: 280 },  icon: { width: 16, height: 16, color: "var(--muted)" },});export function WithIconPrefixAndTextSuffix() {  return (    <TextField xstyle={styles.field} name="website">      <Label>Website</Label>      <InputGroup>        <InputGroup.Prefix>          <Globe aria-hidden="true" {...stylex.props(styles.icon)} />        </InputGroup.Prefix>        <InputGroup.Input xstyle={styles.field} defaultValue="heroui" />        <InputGroup.Suffix>.com</InputGroup.Suffix>      </InputGroup>    </TextField>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

复制按钮后缀

在后缀中加入交互按钮,例如复制按钮。

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";// Adapted from HeroUI v3.2.6, Apache-2.0.import { Copy } from "@gravity-ui/icons";import { Button, InputGroup, Label, TextField } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({  field: { width: "100%", maxWidth: 280 },  suffix: { paddingInlineEnd: 0 },  icon: { width: 16, height: 16 },});export function WithCopySuffix() {  return (    <TextField xstyle={styles.field} name="website">      <Label>Website</Label>      <InputGroup>        <InputGroup.Input xstyle={styles.field} defaultValue="heroui.com" />        <InputGroup.Suffix xstyle={styles.suffix}>          <Button isIconOnly aria-label="Copy" size="sm" variant="ghost">            <Copy aria-hidden="true" {...stylex.props(styles.icon)} />          </Button>        </InputGroup.Suffix>      </InputGroup>    </TextField>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

图标前缀与复制按钮

组合图标前缀与交互式后缀按钮。

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";// Adapted from HeroUI v3.2.6, Apache-2.0.import { Copy, Globe } from "@gravity-ui/icons";import { Button, InputGroup, Label, TextField } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({  field: { width: "100%", maxWidth: 280 },  suffix: { paddingInlineEnd: 0 },  icon: { width: 16, height: 16 },  muted: { color: "var(--muted)" },});export function WithIconPrefixAndCopySuffix() {  return (    <TextField xstyle={styles.field} name="website">      <Label>Website</Label>      <InputGroup>        <InputGroup.Prefix>          <Globe aria-hidden="true" {...stylex.props(styles.icon, styles.muted)} />        </InputGroup.Prefix>        <InputGroup.Input xstyle={styles.field} defaultValue="heroui.com" />        <InputGroup.Suffix xstyle={styles.suffix}>          <Button isIconOnly aria-label="Copy" size="sm" variant="ghost">            <Copy aria-hidden="true" {...stylex.props(styles.icon)} />          </Button>        </InputGroup.Suffix>      </InputGroup>    </TextField>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

密码可见性切换

在后缀中使用按钮切换密码可见性。

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";// Adapted from HeroUI v3.2.6, Apache-2.0.import { Eye, EyeSlash } from "@gravity-ui/icons";import { Button, InputGroup, Label, TextField } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";import { useState } from "react";const styles = stylex.create({  field: { width: "100%", maxWidth: 280 },  suffix: { paddingInlineEnd: 0 },  icon: { width: 16, height: 16 },});export function PasswordWithToggle() {  const [isVisible, setIsVisible] = useState(false);  return (    <TextField xstyle={styles.field} name="password">      <Label>Password</Label>      <InputGroup>        <InputGroup.Input          xstyle={styles.field}          type={isVisible ? "text" : "password"}          value={isVisible ? "87$2h.3diua" : "••••••••"}          readOnly        />        <InputGroup.Suffix xstyle={styles.suffix}>          <Button            isIconOnly            aria-label={isVisible ? "Hide password" : "Show password"}            size="sm"            variant="ghost"            onClick={() => setIsVisible(!isVisible)}          >            {isVisible ? (              <Eye aria-hidden="true" {...stylex.props(styles.icon)} />            ) : (              <EyeSlash aria-hidden="true" {...stylex.props(styles.icon)} />            )}          </Button>        </InputGroup.Suffix>      </InputGroup>    </TextField>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

键盘快捷键

使用 Kbd 组件展示键盘快捷键。

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";// Adapted from HeroUI v3.2.6, Apache-2.0.import { InputGroup, Kbd, TextField } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({  field: { width: "100%", maxWidth: 280 },  suffix: { paddingInlineEnd: 8 },});export function WithKeyboardShortcut() {  return (    <TextField xstyle={styles.field} name="command">      <InputGroup>        <InputGroup.Input aria-label="Command" xstyle={styles.field} placeholder="Command" />        <InputGroup.Suffix xstyle={styles.suffix}>          <Kbd>            <Kbd.Abbr keyValue="command" />            <Kbd.Content>K</Kbd.Content>          </Kbd>        </InputGroup.Suffix>      </InputGroup>    </TextField>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

徽章后缀

在后缀中加入徽章或 chip,用于展示状态或标签。

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";// Adapted from HeroUI v3.2.6, Apache-2.0.import { Chip, InputGroup, TextField } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({  field: { width: "100%", maxWidth: 280 },  suffix: { paddingInlineEnd: 8 },});export function WithBadgeSuffix() {  return (    <TextField xstyle={styles.field} name="email">      <InputGroup>        <InputGroup.Input          aria-label="Email address"          xstyle={styles.field}          placeholder="Email address"        />        <InputGroup.Suffix xstyle={styles.suffix}>          <Chip color="accent" size="md" variant="soft">            Pro          </Chip>        </InputGroup.Suffix>      </InputGroup>    </TextField>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

表单校验

InputGroup 会自动反映父级 TextField 的无效状态。

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";// Adapted from HeroUI v3.2.6, Apache-2.0.import { Envelope } from "@gravity-ui/icons";import { FieldError, InputGroup, Label, TextField } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({  stack: { display: "flex", flexDirection: "column", gap: 16 },  field: { width: "100%", maxWidth: 280 },  price: { width: "100%", maxWidth: 200 },  icon: { width: 16, height: 16, color: "var(--muted)" },});export function Invalid() {  return (    <div {...stylex.props(styles.stack)}>      <TextField invalid xstyle={styles.field} name="email">        <Label required>Email address</Label>        <InputGroup>          <InputGroup.Prefix>            <Envelope aria-hidden="true" {...stylex.props(styles.icon)} />          </InputGroup.Prefix>          <InputGroup.Input required xstyle={styles.field} placeholder="[email protected]" />        </InputGroup>        <FieldError match>Please enter a valid email address</FieldError>      </TextField>      <TextField invalid xstyle={styles.field} name="price">        <Label required>Set a price</Label>        <InputGroup>          <InputGroup.Prefix>$</InputGroup.Prefix>          <InputGroup.Input required xstyle={styles.price} placeholder="0" type="number" />          <InputGroup.Suffix>USD</InputGroup.Suffix>        </InputGroup>        <FieldError match>Price must be greater than 0</FieldError>      </TextField>    </div>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

前缀图标

在输入框前添加图标。

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";// Adapted from HeroUI v3.2.6, Apache-2.0.import { Envelope } from "@gravity-ui/icons";import { Description, InputGroup, Label, TextField } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({  field: { width: "100%", maxWidth: 280 },  icon: { width: 16, height: 16, color: "var(--muted)" },});export function WithPrefixIcon() {  return (    <TextField xstyle={styles.field} name="email">      <Label>Email address</Label>      <InputGroup>        <InputGroup.Prefix>          <Envelope aria-hidden="true" {...stylex.props(styles.icon)} />        </InputGroup.Prefix>        <InputGroup.Input xstyle={styles.field} placeholder="[email protected]" />      </InputGroup>      <Description>We'll never share this with anyone else</Description>    </TextField>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

后缀图标

在输入框后添加图标。

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";// Adapted from HeroUI v3.2.6, Apache-2.0.import { Envelope } from "@gravity-ui/icons";import { Description, InputGroup, Label, TextField } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({  field: { width: "100%", maxWidth: 280 },  icon: { width: 16, height: 16, color: "var(--muted)" },});export function WithSuffixIcon() {  return (    <TextField xstyle={styles.field} name="email">      <Label>Email address</Label>      <InputGroup>        <InputGroup.Input xstyle={styles.field} placeholder="[email protected]" />        <InputGroup.Suffix>          <Envelope aria-hidden="true" {...stylex.props(styles.icon)} />        </InputGroup.Suffix>      </InputGroup>      <Description>We don't send spam</Description>    </TextField>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

前缀与后缀

同时组合前缀与后缀。

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";// Adapted from HeroUI v3.2.6, Apache-2.0.import { Description, InputGroup, Label, TextField } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({  field: { width: "100%", maxWidth: 280 },  input: { width: "100%", maxWidth: 200 },});export function WithPrefixAndSuffix() {  return (    <TextField xstyle={styles.field} name="price">      <Label>Set a price</Label>      <InputGroup>        <InputGroup.Prefix>$</InputGroup.Prefix>        <InputGroup.Input xstyle={styles.input} defaultValue="10" type="number" />        <InputGroup.Suffix>USD</InputGroup.Suffix>      </InputGroup>      <Description>What customers would pay</Description>    </TextField>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

文本域

多行输入请使用 InputGroup.TextArea,并搭配前缀与后缀。当存在 textarea 时,容器高度会自动适应内容,并将前缀/后缀与顶部对齐。

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";// Adapted from HeroUI v3.2.6, Apache-2.0.import { ArrowUp, At, Microphone, PlugConnection, Plus } from "@gravity-ui/icons";import { Button, InputGroup, Kbd, Spinner, TextField, Tooltip } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";import { useState } from "react";const styles = stylex.create({  field: {    display: "flex",    flexDirection: "column",    width: { default: 384, "@media (min-width: 640px)": 512 },  },  group: { display: "flex", flexDirection: "column", gap: 8, borderRadius: 24, paddingBlock: 8 },  prefix: { paddingInline: 12, paddingBlock: 0 },  input: { width: "100%", resize: "none", paddingInline: 14, paddingBlock: 0 },  suffix: {    display: "flex",    width: "100%",    alignItems: "center",    gap: 6,    paddingInline: 12,    paddingBlock: 0,  },  actions: { marginInlineStart: "auto", display: "flex", alignItems: "center", gap: 6 },  tooltip: { display: "flex", alignItems: "center", gap: 4 },  hint: { fontSize: 12, lineHeight: "16px" },  key: { height: 16, borderRadius: "var(--radius-sm)", paddingInline: 4 },});export function WithTextArea() {  const [value, setValue] = useState("");  const [isSubmitting, setIsSubmitting] = useState(false);  const handleSubmit = () => {    if (!value.trim()) return;    setIsSubmitting(true);    setTimeout(() => {      setIsSubmitting(false);      setValue("");    }, 1000);  };  return (    <TextField fullWidth xstyle={styles.field} name="prompt">      <InputGroup fullWidth xstyle={styles.group}>        <InputGroup.Prefix xstyle={styles.prefix}>          <Button aria-label="Add context" size="sm" variant="outline">            <At aria-hidden="true" />            Add Context          </Button>        </InputGroup.Prefix>        <InputGroup.TextArea          aria-label="Prompt input"          xstyle={styles.input}          placeholder="Assign tasks or ask anything..."          rows={5}          value={value}          onChange={(event) => setValue(event.target.value)}        />        <InputGroup.Suffix xstyle={styles.suffix}>          <Tooltip>            <Tooltip.Trigger              delay={0}              render={<Button isIconOnly aria-label="Attach file" size="sm" variant="tertiary" />}            >              <Plus aria-hidden="true" />            </Tooltip.Trigger>            <Tooltip.Portal>              <Tooltip.Positioner>                <Tooltip.Popup>                  <p {...stylex.props(styles.hint)}>Add a files and more</p>                </Tooltip.Popup>              </Tooltip.Positioner>            </Tooltip.Portal>          </Tooltip>          <Tooltip>            <Tooltip.Trigger              delay={0}              render={<Button isIconOnly aria-label="Connect Apps" size="sm" variant="tertiary" />}            >              <PlugConnection aria-hidden="true" />            </Tooltip.Trigger>            <Tooltip.Portal>              <Tooltip.Positioner>                <Tooltip.Popup>                  <p {...stylex.props(styles.hint)}>Connect apps</p>                </Tooltip.Popup>              </Tooltip.Positioner>            </Tooltip.Portal>          </Tooltip>          <div {...stylex.props(styles.actions)}>            <Tooltip>              <Tooltip.Trigger                delay={0}                render={<Button isIconOnly aria-label="Voice input" size="sm" variant="ghost" />}              >                <Microphone aria-hidden="true" />              </Tooltip.Trigger>              <Tooltip.Portal>                <Tooltip.Positioner>                  <Tooltip.Popup>                    <p {...stylex.props(styles.hint)}>Voice input</p>                  </Tooltip.Popup>                </Tooltip.Positioner>              </Tooltip.Portal>            </Tooltip>            <Tooltip>              <Tooltip.Trigger                delay={0}                render={                  <Button                    isIconOnly                    aria-label="Send prompt"                    disabled={!value.trim()}                    isLoading={isSubmitting}                    onClick={handleSubmit}                  />                }              >                {isSubmitting ? (                  <Spinner color="current" size="sm" />                ) : (                  <ArrowUp aria-hidden="true" />                )}              </Tooltip.Trigger>              <Tooltip.Portal>                <Tooltip.Positioner>                  <Tooltip.Popup xstyle={styles.tooltip}>                    <p {...stylex.props(styles.hint)}>Send</p>                    <Kbd xstyle={styles.key}>                      <Kbd.Abbr keyValue="enter" />                    </Kbd>                  </Tooltip.Popup>                </Tooltip.Positioner>              </Tooltip.Portal>            </Tooltip>          </div>        </InputGroup.Suffix>      </InputGroup>    </TextField>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

用法示例

import {InputGroup, TextField, Label, Button} from '@lenso/ui';import {Icon} from '@iconify/react';
function Example() {  return (    <TextField>      <Label>Email</Label>      <InputGroup>        <InputGroup.Prefix>          <Icon icon="gravity-ui:envelope" />        </InputGroup.Prefix>        <InputGroup.Input placeholder="[email protected]" />        <InputGroup.Suffix>          <Button isIconOnly aria-label="Confirm" size="sm" variant="ghost">            <Icon icon="gravity-ui:check" />          </Button>        </InputGroup.Suffix>      </InputGroup>    </TextField>  );}

文本域用法示例

import {Envelope} from "@gravity-ui/icons";import {Description, FieldError, InputGroup, Label, TextField} from "@lenso/ui";import {useState} from "react";
function TextAreaExample() {  const [feedback, setFeedback] = useState("");
  return (    <TextField fullWidth isInvalid={feedback.length > 500} name="feedback" onChange={setFeedback}>      <Label>Your Feedback</Label>      <InputGroup fullWidth>        <InputGroup.Prefix>          <Envelope className="size-4 text-muted" />        </InputGroup.Prefix>        <InputGroup.TextArea          className="resize-none"          placeholder="Share your thoughts, suggestions, or issues..."          rows={5}          value={feedback}        />      </InputGroup>      <Description className="flex w-full items-center justify-between px-1">        <span>Maximum 500 characters.</span>        <span className="ml-auto">{feedback.length}/500</span>      </Description>      <FieldError>Feedback must be less than 500 characters</FieldError>    </TextField>  );}

自定义样式

Tailwind CSS

此预览复用英文版适配,不代表中文源示例已完成本地实现。

"use client";// Adapted from HeroUI v3.2.6, Apache-2.0.import { Envelope } from "@gravity-ui/icons";import { InputGroup, Label, TextField } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({  field: { width: "100%", maxWidth: 320 },  group: {    borderRadius: 12,    borderWidth: 1,    borderStyle: "solid",    borderColor: "color-mix(in oklab, var(--border) 80%, transparent)",    backgroundColor: "var(--default)",    boxShadow: "0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)",  },  icon: { width: 16, height: 16, color: "var(--muted)" },});export function CustomStyles() {  return (    <TextField xstyle={styles.field} name="email">      <Label>Work email</Label>      <InputGroup xstyle={styles.group}>        <InputGroup.Prefix>          <Envelope aria-hidden="true" {...stylex.props(styles.icon)} />        </InputGroup.Prefix>        <InputGroup.Input placeholder="[email protected]" />      </InputGroup>    </TextField>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

全局 CSS

InputGroup 使用可自定义的 CSS 类。覆盖组件类以匹配设计系统。

@layer components {  .input-group {    @apply bg-field text-field-foreground shadow-field rounded-field inline-flex min-h-9 items-center overflow-hidden border text-sm outline-none;  }
  .input-group__input {    @apply flex-1 rounded-none border-0 bg-transparent px-3 py-2 shadow-none outline-none;  }
  .input-group__prefix {    @apply text-field-placeholder rounded-l-field flex h-full items-center justify-center rounded-r-none bg-transparent px-3;  }
  .input-group__suffix {    @apply text-field-placeholder rounded-r-field flex h-full items-center justify-center rounded-l-none bg-transparent px-3;  }
  /* Secondary variant */  .input-group--secondary {    @apply shadow-none;    background-color: var(--color-default);  }}

样式参考

HeroUI 遵循 BEM 方法论,确保组件变体与状态可复用且易于自定义。

CSS 类

基础类 [!toc]

  • .input-group – 根容器:带边框、背景与 flex 布局。默认 min-h-9 与 items-center;存在 textarea 时切换为 items-start
  • .input-group__input – 透明背景、无边框的输入元素。textarea 也使用该基础类
  • .input-group__prefix – 左侧圆角的前缀容器。与 textarea 搭配时与顶部对齐
  • .input-group__suffix – 右侧圆角的后缀容器。与 textarea 搭配时与顶部对齐

变体类 [!toc]

  • .input-group--primary – 带阴影的主变体(默认)
  • .input-group--secondary – 无阴影的次变体,适用于 Surface 内

Note: 使用 InputGroup.TextArea 时,容器从 items-center 切换为 items-start,并使用 height: auto 替代固定高度。前缀与后缀与顶部对齐,并增加内边距以匹配 textarea 的垂直内边距。textarea 使用相同的 .input-group__input 基础类,并通过 [data-slot="input-group-textarea"] 选择器应用 textarea 专用样式(最小高度与纵向 resize)。

交互状态

InputGroup 会根据状态自动管理以下 data 属性:

  • Hover:[data-hovered] - 悬停在组合上时应用
  • Focus Within:[data-focus-within] - 输入聚焦时应用
  • Invalid:[data-invalid] - 父级 TextField 无效时应用
  • Disabled:[data-disabled] 或 [aria-disabled] - 父级 TextField 禁用时应用

API 参考

InputGroup

InputGroup 继承 React Aria Group 组件的全部 props。

Base Props

Prop类型默认值描述
childrenReact.ReactNode | (values: GroupRenderProps) => React.ReactNode-子组件(Input、TextArea、Prefix、Suffix)或 render 函数
classNamestring | (values: GroupRenderProps) => string-CSS 类,支持 render props
styleReact.CSSProperties | (values: GroupRenderProps) => React.CSSProperties-行内样式,支持 render props
fullWidthbooleanfalse输入组是否占满容器宽度
idstring-元素唯一标识符

Variant Props

Prop类型默认值描述
variant"primary" | "secondary""primary"视觉变体。primary 为默认带阴影样式;secondary 为低强调无阴影,适用于 Surface 内

Accessibility Props

Prop类型默认值描述
aria-labelstring-无可见标签时的无障碍标签
aria-labelledbystring-标注该组的元素 ID
aria-describedbystring-描述该组的元素 ID
aria-detailsstring-包含更多详情的元素 ID
role'group' | 'region' | 'presentation''group'分组的无障碍角色。重要内容用 region,纯视觉分组用 presentation

Composition Components

InputGroup 与以下子组件配合使用:

  • InputGroup.Root - 根容器(也可写作 InputGroup)
  • InputGroup.Input - 单行输入元素组件
  • InputGroup.TextArea - 多行 textarea 元素组件
  • InputGroup.Prefix - 前缀容器组件
  • InputGroup.Suffix - 后缀容器组件

InputGroup.Input

InputGroup.Input 继承 React Aria Input 组件的全部 props。

Prop类型默认值描述
classNamestring-CSS 类
variant"primary" | "secondary""primary"输入视觉变体
typestring'text'输入类型(text、password、email 等)
valuestring-当前值(受控)
defaultValuestring-默认值(非受控)
placeholderstring-占位文本
disabledboolean-是否禁用
readOnlyboolean-是否只读

InputGroup.TextArea

InputGroup.TextArea 继承 React Aria TextArea 组件的全部 props。

Prop类型默认值描述
classNamestring-CSS 类
variant"primary" | "secondary""primary"textarea 视觉变体
valuestring-当前值(受控)
defaultValuestring-默认值(非受控)
placeholderstring-占位文本
rowsnumber-可见文本行数
disabledboolean-是否禁用
readOnlyboolean-是否只读

InputGroup.Prefix

Prop类型默认值描述
childrenReact.ReactNode-前缀内容(图标、文字等)
classNamestring-CSS 类

InputGroup.Suffix

Prop类型默认值描述
childrenReact.ReactNode-后缀内容(图标、按钮、徽章等)
classNamestring-CSS 类

相关案例

See upstream InputGroup showcases. Product showcases are not part of the local component runtime.

相关组件