Skip to content
All Taste components

Animated screens

Search

A shop's search that opens out of its icon, takes a query and brings in filters and results.

field expands from icon · typed query · chips slide in · results stagger

Discover

24 results for “ceramic mug”

  • Speckled stoneware mug

    Clay & Kiln · 350 ml

    ₹649
  • Matte glaze mug, sage

    Little Pottery Co. · 300 ml

    ₹799
  • Hand-thrown espresso cup

    Studio Ira · 90 ml

    ₹450
  • Two-tone ceramic mug set

    Clay & Kiln · set of 2

    ₹1,199
  • Tall latte mug, indigo

    Blue Kiln · 420 ml

    ₹699
/* globals.css
@keyframes slide-in { from { opacity: 0; transform: translateX(36px); } }
@keyframes rise { from { opacity: 0; transform: translateY(12px); } }
*/
"use client";

import { useEffect, useRef, useState } from "react";
import { Search, TrendingUp, X } from "lucide-react";
import { cn } from "@/lib/utils";

const products = [
  { name: "Speckled stoneware mug", meta: "Clay & Kiln · 350 ml", price: 649, tags: ["Mugs", "Handmade"] },
  { name: "Matte glaze mug, sage", meta: "Little Pottery Co. · 300 ml", price: 799, tags: ["Mugs"] },
  { name: "Hand-thrown espresso cup", meta: "Studio Ira · 90 ml", price: 450, tags: ["Handmade", "Kitchen"] },
  { name: "Two-tone ceramic mug set", meta: "Clay & Kiln · set of 2", price: 1199, tags: ["Mugs", "Kitchen"] },
  { name: "Tall latte mug, indigo", meta: "Blue Kiln · 420 ml", price: 699, tags: ["Mugs"] },
];
const chips = ["All", "Mugs", "Under ₹1,000", "Handmade", "Kitchen"];
const trending = ["Diwali lights", "Brass diya", "Linen cushions", "Plant stands", "Desk lamp"];
const rupees = (n: number) => "₹" + n.toLocaleString("en-IN");

export default function SearchScreen() {
  const [open, setOpen] = useState(false);
  const [q, setQ] = useState("");
  const [chip, setChip] = useState("All");
  const input = useRef<HTMLInputElement>(null);
  useEffect(() => { if (open) input.current?.focus(); }, [open]);

  const results = products.filter((p) => p.name.toLowerCase().includes(q.trim().toLowerCase())
    && (chip === "All" || (chip === "Under ₹1,000" ? p.price < 1000 : p.tags.includes(chip))));
  const searching = open && q.trim().length > 0;

  return (
    <div className="mx-auto flex min-h-dvh max-w-md flex-col bg-bg font-sans text-fg">
      <header className="relative h-14 shrink-0">
        <h1 className={cn("absolute left-4 top-3 text-2xl font-semibold tracking-tight transition duration-300 ease-taste", open && "-translate-x-3 opacity-0")}>Discover</h1>
        <div role="search" className={cn("absolute right-3 top-1.5 flex h-11 items-center gap-2 overflow-hidden rounded-full border bg-surface px-3 text-fg-muted transition-[width,border-color] duration-500 ease-taste motion-reduce:transition-none",
          open ? "w-[calc(100%-24px)] border-primary" : "w-11 border-border-strong")}>
          <button type="button" aria-label="Search" aria-expanded={open} onClick={() => setOpen(true)} className="-m-1 grid shrink-0 place-items-center p-1"><Search className="size-5" /></button>
          <input ref={input} value={q} onChange={(e) => setQ(e.target.value)} placeholder="Search products" tabIndex={open ? 0 : -1} aria-label="Search products"
            className="min-w-0 flex-1 bg-transparent text-[14.5px] text-fg outline-none placeholder:text-fg-subtle" />
          {open && (
            <button type="button" aria-label="Close search" onClick={() => { setQ(""); setOpen(false); }} className="grid size-6 shrink-0 place-items-center rounded-full bg-surface-2"><X className="size-3.5" /></button>
          )}
        </div>
      </header>

      {searching ? (
        <main className="flex-1">
          <div className="flex gap-2 overflow-x-auto px-4 [scrollbar-width:none]">
            {chips.map((c, i) => (
              <button key={c} type="button" aria-pressed={chip === c} onClick={() => setChip(c)} style={{ animationDelay: i * 70 + "ms" }}
                className="inline-flex h-8 shrink-0 items-center rounded-full border border-border-strong px-3 text-[12.5px] font-medium animate-[slide-in_.45s_cubic-bezier(.2,.7,.1,1)_both] motion-reduce:animate-none aria-pressed:border-primary aria-pressed:bg-primary-soft aria-pressed:text-primary-soft-fg">{c}</button>
            ))}
          </div>
          <p className="mt-2.5 px-4 text-xs text-fg-muted" aria-live="polite">{results.length} results for “{q.trim()}”</p>
          <ul className="px-4">
            {results.map((p, i) => (
              <li key={p.name} style={{ animationDelay: (i + 1) * 90 + "ms" }}
                className="flex min-h-16 items-center gap-3 border-b border-border animate-[rise_.45s_cubic-bezier(.2,.7,.1,1)_both] motion-reduce:animate-none">
                <span aria-hidden className="size-12 shrink-0 rounded bg-[linear-gradient(135deg,var(--tst-surface-2),var(--tst-border-strong))]" />
                <div className="min-w-0 flex-1"><p className="truncate text-sm font-semibold">{p.name}</p><p className="truncate text-xs text-fg-muted">{p.meta}</p></div>
                <span className="text-sm font-semibold tabular-nums">{rupees(p.price)}</span>
              </li>
            ))}
          </ul>
        </main>
      ) : (
        <main className="flex-1 px-4">
          <p className="text-xs font-medium text-fg-muted">Trending searches</p>
          <div className="mt-2 flex flex-wrap gap-2">
            {trending.map((t) => (
              <button key={t} type="button" onClick={() => { setOpen(true); setQ(t); }} className="inline-flex h-8 items-center gap-1 rounded-full border border-border-strong px-3 text-[12.5px] font-medium"><TrendingUp className="size-3.5" />{t}</button>
            ))}
          </div>
        </main>
      )}
    </div>
  );
}

The code uses the Taste Tailwind preset. Make yours on the Taste page with Export my system.