Skip to content

SearchField

Search input field with clear button and search icon.

Import

ts
import {
  SearchField,
  SearchFieldClearButton,
  SearchFieldGroup,
  SearchFieldInput,
  SearchFieldSearchIcon,
} from '@heroui-vue/vue'

Usage

<template>
  <SearchField name="search">
    <Label>Search</Label>
    <SearchFieldGroup>
      <SearchFieldSearchIcon />
      <SearchFieldInput class="demo-search-field-input" placeholder="Search..." />
      <SearchFieldClearButton />
    </SearchFieldGroup>
  </SearchField>
</template>

<script setup lang="ts">
import { Label, SearchField, SearchFieldClearButton, SearchFieldGroup, SearchFieldInput, SearchFieldSearchIcon } from '@heroui-vue/vue'
</script>

<style lang="less">
.demo-search-field-input {
  width: 17.5rem;
}
</style>

Anatomy

vue
<SearchField>
  <Label />
  <SearchFieldGroup>
    <SearchFieldSearchIcon />
    <SearchFieldInput />
    <SearchFieldClearButton />
  </SearchFieldGroup>
  <Description />
  <FieldError />
</SearchField>

With Description

Enter keywords to search for products
Search by name, email, or username

<template>
  <div class="demo-search-field-stack">
    <SearchField name="search">
      <Label>Search products</Label>
      <SearchFieldGroup>
        <SearchFieldSearchIcon />
        <SearchFieldInput class="demo-search-field-input" placeholder="Search products..." />
        <SearchFieldClearButton />
      </SearchFieldGroup>
      <Description>Enter keywords to search for products</Description>
    </SearchField>

    <SearchField name="search-users">
      <Label>Search users</Label>
      <SearchFieldGroup>
        <SearchFieldSearchIcon />
        <SearchFieldInput class="demo-search-field-input" placeholder="Search users..." />
        <SearchFieldClearButton />
      </SearchFieldGroup>
      <Description>Search by name, email, or username</Description>
    </SearchField>
  </div>
</template>

<script setup lang="ts">
import { Description, Label, SearchField, SearchFieldClearButton, SearchFieldGroup, SearchFieldInput, SearchFieldSearchIcon } from '@heroui-vue/vue'
</script>

<style lang="less">
.demo-search-field-stack {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1rem;
}

.demo-search-field-input {
  width: 17.5rem;
}
</style>

Required Field

Minimum 3 characters required

<template>
  <div class="demo-search-field-stack">
    <SearchField name="search" is-required>
      <Label>Search</Label>
      <SearchFieldGroup>
        <SearchFieldSearchIcon />
        <SearchFieldInput class="demo-search-field-input" placeholder="Search..." />
        <SearchFieldClearButton />
      </SearchFieldGroup>
    </SearchField>

    <SearchField name="search-query" is-required>
      <Label>Search query</Label>
      <SearchFieldGroup>
        <SearchFieldSearchIcon />
        <SearchFieldInput class="demo-search-field-input" placeholder="Enter search query..." />
        <SearchFieldClearButton />
      </SearchFieldGroup>
      <Description>Minimum 3 characters required</Description>
    </SearchField>
  </div>
</template>

<script setup lang="ts">
import { Description, Label, SearchField, SearchFieldClearButton, SearchFieldGroup, SearchFieldInput, SearchFieldSearchIcon } from '@heroui-vue/vue'
</script>

<style lang="less">
.demo-search-field-stack {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1rem;
}

.demo-search-field-input {
  width: 17.5rem;
}
</style>

Validation

Search query must be at least 3 characters
Invalid characters in search query

<template>
  <div class="demo-search-field-stack">
    <SearchField name="search" value="ab" is-required is-invalid>
      <Label>Search</Label>
      <SearchFieldGroup>
        <SearchFieldSearchIcon />
        <SearchFieldInput class="demo-search-field-input" placeholder="Search..." />
        <SearchFieldClearButton />
      </SearchFieldGroup>
      <FieldError>Search query must be at least 3 characters</FieldError>
    </SearchField>

    <SearchField name="search-invalid" value="invalid@query" is-invalid>
      <Label>Search</Label>
      <SearchFieldGroup>
        <SearchFieldSearchIcon />
        <SearchFieldInput class="demo-search-field-input" placeholder="Search..." />
        <SearchFieldClearButton />
      </SearchFieldGroup>
      <FieldError>Invalid characters in search query</FieldError>
    </SearchField>
  </div>
</template>

<script setup lang="ts">
import { FieldError, Label, SearchField, SearchFieldClearButton, SearchFieldGroup, SearchFieldInput, SearchFieldSearchIcon } from '@heroui-vue/vue'
</script>

<style lang="less">
.demo-search-field-stack {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1rem;
}

.demo-search-field-input {
  width: 17.5rem;
}
</style>

Disabled State

This search field is disabled
This search field is disabled

<template>
  <div class="demo-search-field-stack">
    <SearchField name="search" value="Disabled search" is-disabled>
      <Label>Search</Label>
      <SearchFieldGroup>
        <SearchFieldSearchIcon />
        <SearchFieldInput class="demo-search-field-input" placeholder="Search..." />
        <SearchFieldClearButton />
      </SearchFieldGroup>
      <Description>This search field is disabled</Description>
    </SearchField>

    <SearchField name="search-empty" is-disabled>
      <Label>Search</Label>
      <SearchFieldGroup>
        <SearchFieldSearchIcon />
        <SearchFieldInput class="demo-search-field-input" placeholder="Search..." />
        <SearchFieldClearButton />
      </SearchFieldGroup>
      <Description>This search field is disabled</Description>
    </SearchField>
  </div>
</template>

<script setup lang="ts">
import { Description, Label, SearchField, SearchFieldClearButton, SearchFieldGroup, SearchFieldInput, SearchFieldSearchIcon } from '@heroui-vue/vue'
</script>

<style lang="less">
.demo-search-field-stack {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1rem;
}

.demo-search-field-input {
  width: 17.5rem;
}
</style>

Controlled

Current value: (empty)

<template>
  <div class="demo-search-field-stack">
    <SearchField v-model="value" name="search">
      <Label>Search</Label>
      <SearchFieldGroup>
        <SearchFieldSearchIcon />
        <SearchFieldInput class="demo-search-field-input" placeholder="Search..." />
        <SearchFieldClearButton />
      </SearchFieldGroup>
      <Description>Current value: {{ value || '(empty)' }}</Description>
    </SearchField>

    <div class="demo-search-field-actions">
      <Button variant="tertiary" @click="value = ''">Clear</Button>
      <Button variant="tertiary" @click="value = 'example query'">Set example</Button>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { Button, Description, Label, SearchField, SearchFieldClearButton, SearchFieldGroup, SearchFieldInput, SearchFieldSearchIcon } from '@heroui-vue/vue'

const value = ref('')
</script>

<style lang="less">
.demo-search-field-stack {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1rem;
}

.demo-search-field-input {
  width: 17.5rem;
}

.demo-search-field-actions {
  display: flex;
  gap: 0.5rem;
}
</style>

With Validation

Enter at least 3 characters to search

<template>
  <SearchField v-model="value" name="search" is-required :is-invalid="isInvalid">
    <Label>Search</Label>
    <SearchFieldGroup>
      <SearchFieldSearchIcon />
      <SearchFieldInput class="demo-search-field-input" placeholder="Search..." />
      <SearchFieldClearButton />
    </SearchFieldGroup>
    <FieldError v-if="isInvalid">Search query must be at least 3 characters</FieldError>
    <Description v-else>Enter at least 3 characters to search</Description>
  </SearchField>
</template>

<script setup lang="ts">
import { computed, ref } from 'vue'
import { Description, FieldError, Label, SearchField, SearchFieldClearButton, SearchFieldGroup, SearchFieldInput, SearchFieldSearchIcon } from '@heroui-vue/vue'

const value = ref('')
const isInvalid = computed(() => value.value.length > 0 && value.value.length < 3)
</script>

<style lang="less">
.demo-search-field-input {
  width: 17.5rem;
}
</style>

Custom Icons

Custom icon children

<template>
  <SearchField name="search-custom">
    <Label>Search (Custom Icons)</Label>
    <SearchFieldGroup>
      <SearchFieldSearchIcon>
        <svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg">
          <path
            clip-rule="evenodd"
            d="M12.5 4c0 .174-.071.513-.885.888S9.538 5.5 8 5.5s-2.799-.237-3.615-.612C3.57 4.513 3.5 4.174 3.5 4s.071-.513.885-.888S6.462 2.5 8 2.5s2.799.237 3.615.612c.814.375.885.714.885.888m-1.448 2.66C10.158 6.888 9.115 7 8 7s-2.158-.113-3.052-.34l1.98 2.905c.21.308.322.672.322 1.044v3.37q.088.02.25.021c.422 0 .749-.14.95-.316c.185-.162.3-.38.3-.684v-2.39c0-.373.112-.737.322-1.045zM8 1c3.314 0 6 1 6 3a3.24 3.24 0 0 1-.563 1.826l-3.125 4.584a.35.35 0 0 0-.062.2V13c0 1.5-1.25 2.5-2.75 2.5s-1.75-1-1.75-1v-3.89a.35.35 0 0 0-.061-.2L2.563 5.826A3.24 3.24 0 0 1 2 4c0-2 2.686-3 6-3m-.88 12.936q-.015-.008-.013-.01z"
            fill="currentColor"
            fill-rule="evenodd"
          />
        </svg>
      </SearchFieldSearchIcon>
      <SearchFieldInput class="demo-search-field-input" placeholder="Search..." />
      <SearchFieldClearButton>
        <svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg">
          <path
            clip-rule="evenodd"
            d="M8 15A7 7 0 1 0 8 1a7 7 0 0 0 0 14M6.53 5.47a.75.75 0 0 0-1.06 1.06L6.94 8L5.47 9.47a.75.75 0 1 0 1.06 1.06L8 9.06l1.47 1.47a.75.75 0 1 0 1.06-1.06L9.06 8l1.47-1.47a.75.75 0 1 0-1.06-1.06L8 6.94z"
            fill="currentColor"
            fill-rule="evenodd"
          />
        </svg>
      </SearchFieldClearButton>
    </SearchFieldGroup>
    <Description>Custom icon children</Description>
  </SearchField>
</template>

<script setup lang="ts">
import { Description, Label, SearchField, SearchFieldClearButton, SearchFieldGroup, SearchFieldInput, SearchFieldSearchIcon } from '@heroui-vue/vue'
</script>

<style lang="less">
.demo-search-field-input {
  width: 17.5rem;
}
</style>

Full Width

<template>
  <div class="demo-search-field-width">
    <SearchField full-width name="search">
      <Label>Search</Label>
      <SearchFieldGroup>
        <SearchFieldSearchIcon />
        <SearchFieldInput placeholder="Search..." />
        <SearchFieldClearButton />
      </SearchFieldGroup>
    </SearchField>
  </div>
</template>

<script setup lang="ts">
import { Label, SearchField, SearchFieldClearButton, SearchFieldGroup, SearchFieldInput, SearchFieldSearchIcon } from '@heroui-vue/vue'
</script>

<style lang="less">
.demo-search-field-width {
  width: 25rem;
}
</style>

Variants

<template>
  <div class="demo-search-field-stack">
    <SearchField name="primary-search" variant="primary">
      <Label>Primary variant</Label>
      <SearchFieldGroup>
        <SearchFieldSearchIcon />
        <SearchFieldInput class="demo-search-field-input" placeholder="Search..." />
        <SearchFieldClearButton />
      </SearchFieldGroup>
    </SearchField>

    <SearchField name="secondary-search" variant="secondary">
      <Label>Secondary variant</Label>
      <SearchFieldGroup>
        <SearchFieldSearchIcon />
        <SearchFieldInput class="demo-search-field-input" placeholder="Search..." />
        <SearchFieldClearButton />
      </SearchFieldGroup>
    </SearchField>
  </div>
</template>

<script setup lang="ts">
import { Label, SearchField, SearchFieldClearButton, SearchFieldGroup, SearchFieldInput, SearchFieldSearchIcon } from '@heroui-vue/vue'
</script>

<style lang="less">
.demo-search-field-stack {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1rem;
}

.demo-search-field-input {
  width: 17.5rem;
}
</style>

In Surface

Use variant="secondary" when placing SearchField inside a Surface.

Enter keywords to search
Use filters to refine your search

<template>
  <Surface class="demo-search-field-surface">
    <SearchField name="search" variant="secondary">
      <Label>Search</Label>
      <SearchFieldGroup>
        <SearchFieldSearchIcon />
        <SearchFieldInput class="demo-search-field-full" placeholder="Search..." />
        <SearchFieldClearButton />
      </SearchFieldGroup>
      <Description>Enter keywords to search</Description>
    </SearchField>

    <SearchField name="search-2" variant="secondary">
      <Label>Advanced search</Label>
      <SearchFieldGroup>
        <SearchFieldSearchIcon />
        <SearchFieldInput class="demo-search-field-full" placeholder="Advanced search..." />
        <SearchFieldClearButton />
      </SearchFieldGroup>
      <Description>Use filters to refine your search</Description>
    </SearchField>
  </Surface>
</template>

<script setup lang="ts">
import { Description, Label, SearchField, SearchFieldClearButton, SearchFieldGroup, SearchFieldInput, SearchFieldSearchIcon, Surface } from '@heroui-vue/vue'
</script>

<style lang="less">
.demo-search-field-surface {
  display: flex;
  width: 100%;
  max-width: 24rem;
  flex-direction: column;
  gap: 1rem;
  border-radius: 1.5rem;
  padding: 1.5rem;
}

.demo-search-field-full {
  width: 100%;
}
</style>

Form Example

Enter at least 3 characters to search

<template>
  <form class="demo-search-field-form" @submit.prevent="handleSubmit">
    <SearchField v-model="value" name="search" is-required :is-invalid="isInvalid">
      <Label>Search products</Label>
      <SearchFieldGroup>
        <SearchFieldSearchIcon />
        <SearchFieldInput class="demo-search-field-full" placeholder="Search products..." />
        <SearchFieldClearButton />
      </SearchFieldGroup>
      <FieldError v-if="isInvalid">Search query must be at least {{ minLength }} characters</FieldError>
      <Description v-else>Enter at least {{ minLength }} characters to search</Description>
    </SearchField>

    <Button class="demo-search-field-full" :is-disabled="value.length < minLength" :is-pending="isSubmitting" type="submit" variant="primary">
      <Spinner v-if="isSubmitting" color="current" size="sm" />
      {{ isSubmitting ? 'Searching...' : 'Search' }}
    </Button>
  </form>
</template>

<script setup lang="ts">
import { computed, ref } from 'vue'
import { Button, Description, FieldError, Label, SearchField, SearchFieldClearButton, SearchFieldGroup, SearchFieldInput, SearchFieldSearchIcon, Spinner } from '@heroui-vue/vue'

const value = ref('')
const isSubmitting = ref(false)
const minLength = 3
const isInvalid = computed(() => value.value.length > 0 && value.value.length < minLength)

const handleSubmit = () => {
  if (value.value.length < minLength) return
  isSubmitting.value = true
  window.setTimeout(() => {
    value.value = ''
    isSubmitting.value = false
  }, 1500)
}
</script>

<style lang="less">
.demo-search-field-form {
  display: flex;
  width: 17.5rem;
  flex-direction: column;
  align-items: flex-start;
  gap: 1rem;
}

.demo-search-field-full {
  width: 100%;
}
</style>

With Keyboard Shortcut

Use keyboard shortcut to quickly focus this field
PressSto focus the search field

<template>
  <div class="demo-search-field-stack">
    <SearchField v-model="value" name="search">
      <Label>Search</Label>
      <SearchFieldGroup>
        <SearchFieldSearchIcon />
        <SearchFieldInput id="shortcut-search" class="demo-search-field-input" placeholder="Search..." />
        <SearchFieldClearButton />
      </SearchFieldGroup>
      <Description>Use keyboard shortcut to quickly focus this field</Description>
    </SearchField>

    <div class="demo-search-field-shortcut">
      <span>Press</span>
      <Kbd :keys="['⇧']">S</Kbd>
      <span>to focus the search field</span>
    </div>
  </div>
</template>

<script setup lang="ts">
import { onBeforeUnmount, onMounted, ref } from 'vue'
import { Description, Kbd, Label, SearchField, SearchFieldClearButton, SearchFieldGroup, SearchFieldInput, SearchFieldSearchIcon } from '@heroui-vue/vue'

const value = ref('')

const handleKeydown = (event: KeyboardEvent) => {
  if (event.shiftKey && event.key === 'S' && !event.metaKey && !event.ctrlKey && !event.altKey) {
    event.preventDefault()
    document.getElementById('shortcut-search')?.focus()
  }
  if (event.key === 'Escape' && document.activeElement?.id === 'shortcut-search') {
    ;(document.activeElement as HTMLElement).blur()
  }
}

onMounted(() => window.addEventListener('keydown', handleKeydown))
onBeforeUnmount(() => window.removeEventListener('keydown', handleKeydown))
</script>

<style lang="less">
.demo-search-field-stack {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1rem;
}

.demo-search-field-input {
  width: 17.5rem;
}

.demo-search-field-shortcut {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  color: var(--color-muted);
  font-size: 0.875rem;
}
</style>

Custom Render Function

<template>
  <SearchField name="search" data-custom="foo">
    <Label>Search</Label>
    <SearchFieldGroup>
      <SearchFieldSearchIcon />
      <SearchFieldInput class="demo-search-field-input" placeholder="Search..." />
      <SearchFieldClearButton />
    </SearchFieldGroup>
  </SearchField>
</template>

<script setup lang="ts">
import { Label, SearchField, SearchFieldClearButton, SearchFieldGroup, SearchFieldInput, SearchFieldSearchIcon } from '@heroui-vue/vue'
</script>

<style lang="less">
.demo-search-field-input {
  width: 17.5rem;
}
</style>

Styling

Use component props for state and variant behavior first. Demo-only layout styles are included in each demo source.

API

PropTypeDefaultDescription
modelValue / valuestringundefinedControlled value
defaultValuestring''Initial uncontrolled value
fullWidthbooleanfalseMakes the field width fill its container
variant'primary' | 'secondary''primary'Visual variant
disabled / isDisabledbooleanfalseDisables the search field
required / isRequiredbooleanfalseMarks the input required
isInvalidbooleanfalseApplies invalid state
namestringundefinedForm field name
EventPayloadDescription
update:modelValuestringEmitted when the value changes
changestringEmitted when the value changes
clearvoidEmitted when clear button is pressed
submitstringEmitted when Enter is pressed

Released under the Apache-2.0 License.