How to Create a Beautiful Search Bar with Expanding Animation

A beautiful search bar with an expanding animation gives your interface a polished, modern feel. It saves space when idle, then grows as the user is ready to type. In this tutorial, I will guide you step by step, using plain language, so you can build an expanding search bar even if you are just getting started with HTML and CSS.

You will get two working examples you can copy and run. The first is a quick, minimal version that relies on HTML and CSS. The second is a complete example with accessibility in mind, a clear button, keyboard behavior, and a friendly look that adapts to mobile screens.

By the end, you will know how to create a beautiful search bar with expanding animation, and you will understand why each part of the code exists.

What you will learn

  • How to use :focus-within to detect when the input is active, then expand the container
  • How transition and width work together to animate smoothly
  • How flex makes the input fill the remaining space
  • How to add accessible labels and focus styles, so keyboard and screen reader users have a good experience

Example 1: Expanding search bar

This first version is short and simple. The idea is straightforward: wrap the input and button in a div, then use :focus-within to expand the width when the user clicks or taps the input. The button uses an inline SVG magnifier icon to avoid image files.

Key idea in plain words: when any child inside .search is focused, the selector .search:focus-within becomes active. I use that to change the width and add a glow. The property transition: width 240ms ease then animates the change.

Here is the complete, self contained HTML you can paste into a file and open in your browser.

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Quick Expanding Search Bar</title>
<style>
:root {
--bg: #0f172a;
--card: #ffffff;
--accent: #2563eb;
--text: #0f172a;
--muted: #64748b;
--ring: rgba(37, 99, 235, 0.35);
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #0b1220;
--card: #111827;
--text: #e5e7eb;
--muted: #9ca3af;
--ring: rgba(96, 165, 250, 0.45);
}
}
body {
margin: 0;
min-height: 100svh;
display: grid;
place-items: center;
background: var(--bg);
font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Arial, Noto Sans, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
color: var(--text);
}
.wrap {
width: min(92vw, 720px);
padding: 24px;
background: var(--card);
border-radius: 16px;
box-shadow: 0 10px 30px rgba(0,0,0,.18);
}
.search {
display: flex;
align-items: center;
gap: .5rem;
width: min(70vw, 280px);
transition: width 240ms ease;
background: white;
border: 1px solid #e5e7eb;
border-radius: 999px;
padding: .5rem .5rem .5rem .75rem;
box-shadow: 0 1px 0 rgba(0,0,0,.03);
}
.search:focus-within {
width: min(92vw, 540px);
border-color: var(--accent);
box-shadow: 0 0 0 4px var(--ring), 0 6px 18px rgba(0,0,0,.12);
}
.search input[type="search"] {
flex: 1;
min-width: 0;
border: 0;
outline: none;
background: transparent;
font-size: 1rem;
color: #111827;
}
.search input::placeholder { color: var(--muted); }
.search button {
border: 0;
background: var(--accent);
color: #fff;
padding: .5rem .75rem;
border-radius: 999px;
cursor: pointer;
}
.search button:focus-visible { outline: 3px solid var(--ring); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { .search { transition: none; } }
</style>
</head>
<body>
<div class="wrap">
<div class="search" role="search">
<input type="search" placeholder="Quick search" aria-label="Quick search">
<button type="button" aria-label="Submit search">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" aria-hidden="true">
<circle cx="11" cy="11" r="7"></circle>
<path d="M21 21l-3.5-3.5"></path>
</svg>
</button>
</div>
</div>
</body>
</html>

Output:

Responsive expanding search bar with left magnifying glass icon, input field, clear x button, and blue Search button in expanded state.

A few lines to notice:

  • .search:focus-within { width: min(92vw, 540px); } makes the pill expand. min(92vw, 540px) means the width will be the smaller of 92 percent of the viewport or 540 pixels.
  • .search input { flex: 1; } tells the input to fill the remaining space inside the flex container.
  • .search input { outline: none; } removes the default outline; I replace it with a visible focus style on the button using :focus-visible. You could add a similar outline for the input if you prefer.

Example 2: Complete beautiful search bar with expanding animation

Now let me build a more complete version. It is responsive, accessible, and it has a clear button. When the input is focused or has text, the container expands and the shadow increases. I also include a small script to handle clearing the field and to show the typed query, so you can test quickly.

Paste this into a file and open it in your browser.

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Beautiful Search Bar with Expanding Animation - Complete Example</title>
<style>
:root {
--bg: #0f172a;
--panel: #ffffff;
--panelElev: #ffffff;
--line: #e5e7eb;
--text: #0f172a;
--muted: #6b7280;
--accent: #2563eb;
--accent-600: #1d4ed8;
--ring: rgba(37, 99, 235, 0.35);
--shadow: 0 2px 8px rgba(0,0,0,.07), 0 10px 24px rgba(0,0,0,.10);
--shadow-lg: 0 12px 30px rgba(0,0,0,.16);
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #0b1220;
--panel: #0f172a;
--panelElev: #111827;
--line: #243244;
--text: #e5e7eb;
--muted: #9ca3af;
--ring: rgba(96, 165, 250, 0.45);
--shadow: 0 2px 8px rgba(0,0,0,.4), 0 10px 24px rgba(0,0,0,.55);
--shadow-lg: 0 12px 30px rgba(0,0,0,.6);
}
}
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100svh;
display: grid;
place-items: center;
background: radial-gradient(1200px 500px at 50% -100px, rgba(37,99,235,.25), transparent 60%), var(--bg);
font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Arial, Noto Sans, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
color: var(--text);
}
.container {
width: min(92vw, 900px);
padding: 24px;
background: var(--panelElev);
border-radius: 16px;
box-shadow: var(--shadow);
}
h1 { margin: 0 0 16px; font-size: clamp(1.25rem, 2.6vw, 1.7rem); }
p { margin: 0 0 12px; color: var(--muted); }
/* Search bar */
.searchbar {
--h: 48px;
display: flex;
align-items: center;
gap: 10px;
height: var(--h);
width: min(90vw, 360px);
padding: 6px 8px 6px 12px;
border: 1px solid var(--line);
border-radius: 999px;
background: var(--panel);
box-shadow: var(--shadow);
transition: width 320ms cubic-bezier(.2,.7,.2,1), box-shadow 200ms ease, background-color 200ms ease, border-color 200ms ease;
}
.searchbar:focus-within,
.searchbar.has-value {
width: min(96vw, 720px);
border-color: color-mix(in oklab, var(--accent) 25%, var(--line));
box-shadow: var(--shadow-lg);
}
.searchbar .leading-icon {
width: 28px;
height: 28px;
display: grid;
place-items: center;
border-radius: 999px;
color: var(--accent);
background: color-mix(in oklab, var(--accent) 14%, transparent);
flex: 0 0 auto;
}
.searchbar input[type="search"] {
flex: 1 1 auto;
min-width: 0;
height: calc(var(--h) - 12px);
border: 0;
background: transparent;
outline: none;
font-size: 1rem;
color: var(--text);
}
.searchbar input::placeholder { color: var(--muted); }
.searchbar .clear {
opacity: 0;
transform: scale(.8);
pointer-events: none;
border: 0;
background: transparent;
width: 32px;
height: 32px;
border-radius: 999px;
color: var(--muted);
transition: opacity 140ms ease, transform 140ms ease, background-color 140ms ease;
}
.searchbar.has-value .clear {
opacity: 1;
transform: scale(1);
pointer-events: auto;
}
.searchbar .clear:hover { background: color-mix(in oklab, var(--muted) 12%, transparent); }
.searchbar .submit {
border: 0;
background: var(--accent);
color: #fff;
padding: 8px 14px;
border-radius: 999px;
font-weight: 600;
height: 36px;
display: inline-grid;
grid-auto-flow: column;
align-items: center;
gap: 8px;
cursor: pointer;
}
.searchbar .submit:hover { background: var(--accent-600); }
.searchbar .submit svg { display: block; }
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0,0,0,0);
white-space: nowrap;
border: 0;
}
.searchbar :is(button, input):focus-visible {
outline: 3px solid var(--ring);
outline-offset: 2px;
}
.demo {
margin-top: 14px;
font-size: .95rem;
color: var(--muted);
}
@media (max-width: 420px) {
.searchbar .submit span { display: none; }
.searchbar .submit { padding-inline: 10px; width: 36px; justify-content: center; }
}
@media (prefers-reduced-motion: reduce) {
.searchbar { transition: none; }
.searchbar .clear { transition: none; }
}
</style>
</head>
<body>
<div class="container">
<h1>Complete Expanding Search Bar</h1>
<p>Try typing into the field and press Enter; the bar expands while you type. You can clear with the x button.</p>

<form id="searchForm" class="searchbar" role="search" aria-label="Site search" autocomplete="off">
<span class="leading-icon" aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
<circle cx="11" cy="11" r="7"></circle>
<path d="M21 21l-3.5-3.5"></path>
</svg>
</span>

<label for="q" class="sr-only">Search</label>
<input id="q" name="q" type="search" placeholder="Search articles, products, or topics">

<button type="button" class="clear" aria-label="Clear search">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" aria-hidden="true">
<path d="M18 6 6 18M6 6l12 12"/>
</svg>
</button>

<button type="submit" class="submit">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" aria-hidden="true">
<circle cx="11" cy="11" r="7"></circle>
<path d="M21 21l-3.5-3.5"></path>
</svg>
<span>Search</span>
</button>
</form>

<div id="output" class="demo" aria-live="polite"></div>
</div>

<script>
const form = document.getElementById('searchForm');
const input = form.querySelector('input[type="search"]');
const clearBtn = form.querySelector('.clear');
const output = document.getElementById('output');

function reflectValueState() {
const has = input.value.trim().length > 0;
form.classList.toggle('has-value', has);
clearBtn.setAttribute('aria-hidden', has ? 'false' : 'true');
}

input.addEventListener('input', reflectValueState);
reflectValueState();

clearBtn.addEventListener('click', () => {
input.value = '';
reflectValueState();
input.focus();
output.textContent = '';
});

form.addEventListener('submit', (e) => {
e.preventDefault();
const q = input.value.trim();
if (!q) { input.focus(); return; }
output.textContent = `You searched for: "${q}"`;
});

// Keyboard helper: Esc clears
input.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && input.value) {
e.preventDefault();
clearBtn.click();
}
});
</script>
</body>
</html>

What makes this example feel complete:

  • Expansion is controlled by .searchbar:focus-within, .searchbar.has-value { width: ... }. The class has-value is toggled in JavaScript when the input has text. That keeps the bar expanded while you read the results.
  • The clear button fades in with .searchbar.has-value .clear { opacity: 1; transform: scale(1); }. When empty, it is hidden and does not catch clicks because pointer-events: none is applied.
  • Accessibility: the wrapper uses role="search", the input has a real <label> with a visually hidden class, and there is a clear aria-label on the clear button. The focus ring comes from :focus-visible so keyboard users can navigate confidently.
  • Respect for user preferences: @media (prefers-reduced-motion: reduce) removes the animation if a user prefers less motion.

If you are new to CSS, here is a mental model that helps. The container is a flexible row with three pieces: a leading icon, the input that grows, and two buttons. The input grows because flex: 1 1 auto allows it to take up leftover space. The width of the container changes on focus, then the browser animates that change because of transition: width 320ms cubic-bezier(.2,.7,.2,1).

Common mistakes and quick fixes

  • The input does not expand: check that the input has flex: 1 and that the parent .search or .searchbar uses display: flex.
  • The animation looks jumpy: reduce the width difference or adjust the easing curve to ease or ease-in-out.
  • Focus outline is missing: ensure you add a visible focus style with :focus-visible on inputs and buttons, for example outline: 3px solid #76a7ff.
  • On very small screens the bar overflows: use min(96vw, some-pixel-value) so it never exceeds the viewport width.

Closing thoughts

You now have two ways to create a beautiful search bar with expanding animation. The first example gives you the core idea with very little code, good for quick prototypes. The second example adds accessibility, clear button behavior, and responsive polish, good for production. Start with the simple version, then bring in the extras when you need them.

Try our: Duplicate CSS Remover

Vinish Kapoor
Vinish Kapoor

Vinish Kapoor is a seasoned software development professional and a fervent enthusiast of artificial intelligence (AI). His impressive career spans over 25+ years, marked by a relentless pursuit of innovation and excellence in the field of information technology. As an Oracle ACE, Vinish has distinguished himself as a leading expert in Oracle technologies, a title awarded to individuals who have demonstrated their deep commitment, leadership, and expertise in the Oracle community.

guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments