InputGroup 输入框组

把前后缀、按钮与输入框合为一个输入面。

https://
.hoshimi.moe
<script setup lang="ts">
  import { ref } from 'vue'
  import { Input, InputGroup, InputGroupAddon } from '@hina-ui/vue'

  const site = ref('shion')
</script>

<template>
  <InputGroup class="w-80">
    <InputGroupAddon>https://</InputGroupAddon>
    <Input v-model="site" aria-label="站点名" />
    <InputGroupAddon>.hoshimi.moe</InputGroupAddon>
  </InputGroup>
</template>
vue

用法

import { InputGroup, InputGroupAddon } from '@hina-ui/vue'
ts

组本身就是一个完整的输入面:边框、阴影与聚焦环都在组上,组内的输入框、附属段与按钮共用这一副外壳。尺寸与形态设置在组上,组内的输入框沿用组的设置。InputGroupAddon 用于固定的前缀或者后缀,文字与图标都可以,以分隔线与输入区区分;边框以内的图标与单位用输入框自己的 leading 与 trailing 插槽。

元
<script setup lang="ts">
  import { ref } from 'vue'
  import { AtSign } from '@lucide/vue'
  import { Input, InputGroup, InputGroupAddon, Stack } from '@hina-ui/vue'

  const handle = ref('')
  const amount = ref('')
</script>

<template>
  <Stack class="w-80">
    <InputGroup>
      <InputGroupAddon>
        <AtSign />
      </InputGroupAddon>
      <Input v-model="handle" aria-label="用户名" placeholder="用户名" />
    </InputGroup>
    <InputGroup>
      <Input v-model="amount" inputmode="decimal" aria-label="金额" placeholder="金额" />
      <InputGroupAddon>元</InputGroupAddon>
    </InputGroup>
  </Stack>
</template>
vue

示例

贴边按钮

按钮直接放入组内,与输入区共用外壳,按下时不缩放。

<script setup lang="ts">
  import { ref } from 'vue'
  import { Copy } from '@lucide/vue'
  import { Button, IconButton, Input, InputGroup, Stack } from '@hina-ui/vue'

  const code = ref('HINA-2026')
  const email = ref('')
</script>

<template>
  <Stack class="w-80">
    <InputGroup>
      <Input v-model="code" readonly aria-label="邀请码" />
      <IconButton label="复制" variant="outline" tone="neutral">
        <Copy />
      </IconButton>
    </InputGroup>
    <InputGroup>
      <Input v-model="email" type="email" aria-label="邮箱" placeholder="邮箱" />
      <Button>订阅</Button>
    </InputGroup>
  </Stack>
</template>
vue

尺寸

尺寸设置在组上,组内的输入框与按钮随之对齐。组内按钮的尺寸仍需与组一致。

https://
https://
https://
<script setup lang="ts">
  import { Button, Input, InputGroup, InputGroupAddon, Stack } from '@hina-ui/vue'
</script>

<template>
  <Stack class="w-80">
    <InputGroup size="sm">
      <InputGroupAddon>https://</InputGroupAddon>
      <Input aria-label="小号" />
      <Button size="sm">确定</Button>
    </InputGroup>
    <InputGroup size="md">
      <InputGroupAddon>https://</InputGroupAddon>
      <Input aria-label="中号" />
      <Button size="md">确定</Button>
    </InputGroup>
    <InputGroup size="lg">
      <InputGroupAddon>https://</InputGroupAddon>
      <Input aria-label="大号" />
      <Button size="lg">确定</Button>
    </InputGroup>
  </Stack>
</template>
vue

形态

primary 带边框、背景与阴影;secondary 使用浅色背景;bare 背景透明,不绘制边框、阴影、悬停底色或容器聚焦环,尺寸与内边距仍由原有设置控制。

bare 保留禁用状态与 aria-invalid;错误信息可由 FormField 显示。组内分隔线也会隐藏,按钮保留自身的交互反馈。

@
@
@
<script setup lang="ts">
  import { Input, InputGroup, InputGroupAddon, Stack } from '@hina-ui/vue'
</script>

<template>
  <Stack class="w-full max-w-xs">
    <InputGroup variant="primary">
      <InputGroupAddon>@</InputGroupAddon>
      <Input aria-label="primary" placeholder="primary" />
    </InputGroup>
    <InputGroup variant="secondary">
      <InputGroupAddon>@</InputGroupAddon>
      <Input aria-label="secondary" placeholder="secondary" />
    </InputGroup>
    <InputGroup variant="bare">
      <InputGroupAddon>@</InputGroupAddon>
      <Input aria-label="bare" placeholder="bare" />
    </InputGroup>
  </Stack>
</template>
vue

状态

invalid 与 disabled 设置在组上,下发到组内的每个输入框。

https://
https://
<script setup lang="ts">
  import { Input, InputGroup, InputGroupAddon, Stack } from '@hina-ui/vue'
</script>

<template>
  <Stack class="w-80">
    <InputGroup invalid>
      <InputGroupAddon>https://</InputGroupAddon>
      <Input model-value="not a domain" aria-label="校验未通过" />
    </InputGroup>
    <InputGroup disabled>
      <InputGroupAddon>https://</InputGroupAddon>
      <Input model-value="shion.hoshimi.moe" aria-label="已禁用" />
    </InputGroup>
  </Stack>
</template>
vue

与其他输入框组合

数值、搜索等输入框都能放入组内。

<script setup lang="ts">
  import { ref } from 'vue'
  import { JapaneseYen } from '@lucide/vue'
  import {
    Button,
    InputGroup,
    InputGroupAddon,
    NumberInput,
    SearchInput,
    Stack,
  } from '@hina-ui/vue'

  const price = ref(128)
  const query = ref('')
</script>

<template>
  <Stack class="w-80">
    <InputGroup>
      <InputGroupAddon>
        <JapaneseYen />
      </InputGroupAddon>
      <NumberInput v-model="price" :min="0" aria-label="价格" />
    </InputGroup>
    <InputGroup>
      <SearchInput v-model="query" aria-label="搜索" placeholder="搜索作品" />
      <Button>搜索</Button>
    </InputGroup>
  </Stack>
</template>
vue

在表单中

整组放进 FormField 后,标签指向组内的输入框,错误信息由字段渲染;校验规则与提交交给 Form。一个字段里只放一个输入框。

https://
<script setup lang="ts">
  import { reactive, ref } from 'vue'
  import * as v from 'valibot'
  import { Button, Form, FormField, Input, InputGroup, InputGroupAddon, Text } from '@hina-ui/vue'

  const schema = v.object({
    site: v.pipe(
      v.string('请输入网址'),
      v.trim(),
      v.nonEmpty('请输入网址'),
      v.regex(/^[\w-]+(\.[\w-]+)+(\/.*)?$/, '网址格式不正确,不需要填写协议'),
    ),
  })

  const values = reactive({ site: '' })
  const saved = ref('')

  async function save(data: unknown) {
    await new Promise(resolve => setTimeout(resolve, 600))
    saved.value = `https://${(data as { site: string }).site}`
  }
</script>

<template>
  <Form v-slot="{ submitting }" :values="values" :rules="schema" class="w-80" @submit="save">
    <FormField name="site" label="个人主页" required>
      <InputGroup>
        <InputGroupAddon>https://</InputGroupAddon>
        <Input v-model="values.site" placeholder="example.com" />
      </InputGroup>
    </FormField>
    <Button type="submit" :loading="submitting" class="self-start">保存</Button>
    <Text v-if="saved" tone="muted" size="sm">已保存:{{ saved }}</Text>
  </Form>
</template>
vue

行为

  • primary 与 secondary 聚焦组内任一输入区时,整个组显示聚焦环。
  • 点击附属段会让相邻的输入区聚焦,前缀把光标放到开头,后缀放到末尾。按钮不受影响。
  • 组内按钮键盘聚焦时轮廓向内收,不会被组的边缘裁掉。
  • 悬停、错误与禁用的表现与 Input 相同。

无障碍

  • 附属段是纯展示内容,不参与输入框的无障碍名称。需要说明时用 label 元素或者 aria-label 给输入框命名。

API

InputGroup

属性
类型
默认值
说明
variant
'primary' | 'secondary' | 'bare'
'primary'
形态
size
'sm' | 'md' | 'lg'
'md'
尺寸
invalid
boolean
false
是否校验未通过
disabled
boolean
false
是否禁用
class
string
—
追加至根元素的类名

InputGroupAddon

属性
类型
默认值
说明
class
string
—
追加至根元素的类名