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
ceramic mug
24 results for “ceramic mug”
- Speckled stoneware mug₹649
Clay & Kiln · 350 ml
- Matte glaze mug, sage₹799
Little Pottery Co. · 300 ml
- Hand-thrown espresso cup₹450
Studio Ira · 90 ml
- Two-tone ceramic mug set₹1,199
Clay & Kiln · set of 2
- Tall latte mug, indigo₹699
Blue Kiln · 420 ml
/* 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.