← Components/Inputs & Forms

SearchInput

searchinput-1779378817631.tsx
'use client'
import { useState, useEffect, useRef } from 'react'

const SUGGESTIONS = ['GlowButton', 'HoverTiltCard', 'CommandMenu', 'DataTable', 'LineChart', 'PricingCards', 'ChatInterface', 'MagicLinkAuth', 'DrawerPanel', 'StatsGrid', 'OTPInput', 'TestimonialCarousel', 'ToastStack', 'ConfirmModal', 'TabsNavigation']
const RECENT = ['GlowButton', 'DataTable', 'ChatInterface']

export default function SearchInput() {
  const [value, setValue] = useState('')
  const [open, setOpen] = useState(false)
  const [cursor, setCursor] = useState(-1)
  const ref = useRef<HTMLInputElement>(null)

  const filtered = value.length > 0
    ? SUGGESTIONS.filter(s => s.toLowerCase().includes(value.toLowerCase())).slice(0, 6)
    : []

  const items = value.length > 0 ? filtered : RECENT

  function select(item: string) {
    setValue(item)
    setOpen(false)
    setCursor(-1)
  }

  function onKey(e: React.KeyboardEvent) {
    if (e.key === 'ArrowDown') { e.preventDefault(); setCursor(c => Math.min(c + 1, items.length - 1)) }
    if (e.key === 'ArrowUp') { e.preventDefault(); setCursor(c => Math.max(c - 1, -1)) }
    if (e.key === 'Enter' && cursor >= 0) select(items[cursor])
    if (e.key === 'Escape') { setOpen(false); setValue('') }
  }

  return (
    <div style={{ padding: 40, background: '#0A0A0A', display: 'flex', justifyContent: 'center', minHeight: 200 }}>
      <div style={{ position: 'relative', width: 360 }}>
        <div style={{ position: 'relative' }}>
          <span style={{ position: 'absolute', left: 12, top: '50%', transform: 'translateY(-50%)', fontSize: 14, color: 'rgba(255,255,255,0.3)' }}>🔍</span>
          <input ref={ref} value={value}
            onChange={e => { setValue(e.target.value); setOpen(true); setCursor(-1) }}
            onFocus={() => setOpen(true)}
            onBlur={() => setTimeout(() => setOpen(false), 150)}
            onKeyDown={onKey}
            placeholder="Search components..."
            style={{ width: '100%', boxSizing: 'border-box', background: 'rgba(255,255,255,0.04)', border: open ? '1.5px solid rgba(201,168,76,0.4)' : '1.5px solid rgba(255,255,255,0.1)', borderRadius: 12, color: '#F5F5F0', fontSize: 14, padding: '12px 40px 12px 36px', outline: 'none', transition: 'border-color 0.2s' }}
          />
          {value && <button onClick={() => { setValue(''); ref.current?.focus() }} style={{ position: 'absolute', right: 10, top: '50%', transform: 'translateY(-50%)', background: 'rgba(255,255,255,0.08)', border: 'none', color: 'rgba(255,255,255,0.5)', borderRadius: '50%', width: 20, height: 20, cursor: 'pointer', fontSize: 12, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>×</button>}
        </div>

        {open && (
          <div style={{ position: 'absolute', top: 'calc(100% + 6px)', left: 0, right: 0, background: '#111', border: '1px solid rgba(255,255,255,0.1)', borderRadius: 12, overflow: 'hidden', boxShadow: '0 16px 48px rgba(0,0,0,0.6)', zIndex: 9999 }}>
            {value === '' && <div style={{ padding: '8px 14px 4px', color: 'rgba(255,255,255,0.3)', fontSize: 11, textTransform: 'uppercase', letterSpacing: '0.08em' }}>Recent</div>}
            {items.length === 0 && <div style={{ padding: '14px', color: 'rgba(255,255,255,0.35)', fontSize: 13 }}>No results for "{value}"</div>}
            {items.map((item, i) => (
              <div key={item} onClick={() => select(item)} style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '10px 14px', cursor: 'pointer', background: cursor === i ? 'rgba(255,255,255,0.06)' : 'transparent', transition: 'background 0.1s' }}>
                <span style={{ fontSize: 13, color: 'rgba(255,255,255,0.25)' }}>{value.length > 0 ? '⬡' : '↻'}</span>
                <span style={{ color: '#F5F5F0', fontSize: 13 }}>
                  {value.length > 0 ? (
                    <>
                      {item.substring(0, item.toLowerCase().indexOf(value.toLowerCase()))}
                      <mark style={{ background: 'rgba(201,168,76,0.25)', color: '#C9A84C', borderRadius: 2 }}>{item.substring(item.toLowerCase().indexOf(value.toLowerCase()), item.toLowerCase().indexOf(value.toLowerCase()) + value.length)}</mark>
                      {item.substring(item.toLowerCase().indexOf(value.toLowerCase()) + value.length)}
                    </>
                  ) : item}
                </span>
              </div>
            ))}
            {value.length > 0 && filtered.length > 0 && (
              <div style={{ padding: '8px 14px', borderTop: '1px solid rgba(255,255,255,0.05)', color: 'rgba(255,255,255,0.3)', fontSize: 12 }}>
                Press Enter to search all results
              </div>
            )}
          </div>
        )}
      </div>
    </div>
  )
}

Component info

CategoryInputs & Forms
Frameworkreact
TierFREE
Views0
Copies0

About

Search input with debounced suggestions, keyboard navigation, recent searches, and clear button

More from Inputs & Forms

'use client';

import { useState, useEffect } from 'react';

interface Tag {
  id: number;
  name: string;
}

const TagInput = () => {
  const [tags, setTags] = useState<Tag[]>([]);
  const [inputValue, setInputValue] = useState('');
  const [suggest
TagInput
Inputs & Forms
'use client';

import React, { useState } from 'react';

interface File {
  name: string;
  size: number;
  progress: number;
}

const FileUpload = () => {
  const [dragOver, setDragOver] = useState(false);
  const [files, setFiles] = useState<File[]
FileUpload
Inputs & Forms
import React, { useState } from 'react';

const OTPInput = () => {
  const [otp, setOtp] = useState(new Array(6).fill(''));
  const [activeIndex, setActiveIndex] = useState(0);

  const handleChange = (e, index) => {
    const value = e.target.value;
OTPInput
Inputs & Forms
'use client';

import React, { useState } from 'react';

interface RangeSliderProps {
  min: number;
  max: number;
  defaultValue: [number, number];
}

const RangeSlider: React.FC<RangeSliderProps> = ({ min, max, defaultValue }) => {
  const [minVal
RangeSlider
Inputs & Forms