Building a Search Bar for Your Firefox New Tab Extension
A search bar is the highest-ROI feature for any new tab extension. Users type queries dozens of times per day — if your extension can save them from navigating to google.com first, that's real value.
Here's how I built the search bar in the Weather & Clock Dashboard extension.
The Basic HTML
<form id="search-form" class="search-form" role="search">
<div class="search-wrapper">
<svg class="search-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<circle cx="11" cy="11" r="8"/>
<path d="m21 21-4.35-4.35"/>
</svg>
<input
type="search"
id="search-input"
class="search-input"
placeholder="Search the web..."
autocomplete="off"
spellcheck="false"
autofocus
/>
</div>
</form>
Handling Form Submission
The key: submit to the right search engine based on user preference.
const searchEngines = {
google: 'https://www.google.com/search?q=',
bing: 'https://www.bing.com/search?q=',
duckduckgo: 'https://duckduckgo.com/?q=',
brave: 'https://search.brave.com/search?q=',
ecosia: 'https://www.ecosia.org/search?q=',
};
document.getElementById('search-form').addEventListener('submit', async (e) => {
e.preventDefault();
const query = document.getElementById('search-input').value.trim();
if (!query) return;
// Check if it's a URL
if (isUrl(query)) {
const url = query.startsWith('http') ? query : `https://${query}`;
window.location.href = url;
return;
}
// Get preferred search engine
const { searchEngine = 'google' } = await browser.storage.local.get('searchEngine');
const baseUrl = searchEngines[searchEngine] || searchEngines.google;
window.location.href = baseUrl + encodeURIComponent(query);
});
function isUrl(text) {
// Basic URL detection: has a dot and no spaces, or starts with http
return /^https?:\/\//.test(text) ||
(/\.[a-z]{2,}/.test(text) && !text.includes(' '));
}
Keyboard Shortcut to Focus Search
document.addEventListener('keydown', (e) => {
// Focus search on '/' key (like many web apps)
if (e.key === '/' && document.activeElement !== document.getElementById('search-input')) {
e.preventDefault();
document.getElementById('search-input').focus();
document.getElementById('search-input').select();
}
// Clear on Escape
if (e.key === 'Escape') {
const input = document.getElementById('search-input');
if (document.activeElement === input) {
input.blur();
input.value = '';
}
}
});
Search Suggestions with OpenSearch
For basic suggestions (Google supports this via their suggest API):
async function getSuggestions(query) {
if (query.length < 2) return [];
try {
// Google suggest API
const url = `https://suggestqueries.google.com/complete/search?client=firefox&q=${encodeURIComponent(query)}`;
const response = await fetch(url);
const data = await response.json();
return data[1] || []; // [query, [suggestions]]
} catch {
return [];
}
}
let suggestTimeout;
document.getElementById('search-input').addEventListener('input', (e) => {
clearTimeout(suggestTimeout);
const query = e.target.value.trim();
if (!query) {
hideSuggestions();
return;
}
suggestTimeout = setTimeout(async () => {
const suggestions = await getSuggestions(query);
showSuggestions(suggestions, query);
}, 200);
});
function showSuggestions(suggestions, query) {
const list = document.getElementById('suggestions-list');
if (!suggestions.length) { hideSuggestions(); return; }
list.innerHTML = suggestions.slice(0, 6).map(s => `
<li class="suggestion" data-query="${escapeHtml(s)}">
<svg class="search-icon-sm">...</svg>
${highlightMatch(s, query)}
</li>
`).join('');
list.classList.remove('hidden');
}
function hideSuggestions() {
document.getElementById('suggestions-list').classList.add('hidden');
}
Search Engine Switcher
Allow users to pick their search engine:
const engineOptions = [
{ id: 'google', name: 'Google', favicon: 'https://google.com/favicon.ico' },
{ id: 'duckduckgo', name: 'DuckDuckGo', favicon: 'https://duckduckgo.com/favicon.ico' },
{ id: 'bing', name: 'Bing', favicon: 'https://bing.com/favicon.ico' },
{ id: 'brave', name: 'Brave', favicon: 'https://brave.com/favicon.ico' },
];
async function buildEngineSelector() {
const { searchEngine = 'google' } = await browser.storage.local.get('searchEngine');
const selector = document.getElementById('engine-selector');
selector.innerHTML = engineOptions.map(e => `
<button class="engine-btn ${e.id === searchEngine ? 'active' : ''}" data-engine="${e.id}">
<img src="${e.favicon}" alt="${e.name}" width="16" height="16">
</button>
`).join('');
selector.addEventListener('click', async (event) => {
const btn = event.target.closest('.engine-btn');
if (!btn) return;
const newEngine = btn.dataset.engine;
await browser.storage.local.set({ searchEngine: newEngine });
// Update active state
selector.querySelectorAll('.engine-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
// Focus search input
document.getElementById('search-input').focus();
});
}
CSS
.search-form {
width: 100%;
max-width: 600px;
margin: 0 auto;
}
.search-wrapper {
position: relative;
display: flex;
align-items: center;
}
.search-input {
width: 100%;
padding: 12px 16px 12px 44px;
font-size: 16px;
border: none;
border-radius: 24px;
background: rgba(255, 255, 255, 0.15);
color: white;
backdrop-filter: blur(10px);
transition: background 0.2s, box-shadow 0.2s;
}
.search-input:focus {
outline: none;
background: rgba(255, 255, 255, 0.25);
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.3);
}
.search-input::placeholder {
color: rgba(255, 255, 255, 0.5);
}
.search-icon {
position: absolute;
left: 14px;
width: 18px;
height: 18px;
color: rgba(255, 255, 255, 0.6);
pointer-events: none;
}
/* Remove browser's default search cancel button */
.search-input::-webkit-search-cancel-button {
display: none;
}
The search bar in Weather & Clock Dashboard supports Google, Bing, DuckDuckGo, and Brave Search with a quick toggle.
Top comments (0)