DEV Community

Weather Clock Dash
Weather Clock Dash

Posted on

Building a Search Bar for Your Firefox New Tab Extension

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>
Enter fullscreen mode Exit fullscreen mode

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(' '));
}
Enter fullscreen mode Exit fullscreen mode

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 = '';
    }
  }
});
Enter fullscreen mode Exit fullscreen mode

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');
}
Enter fullscreen mode Exit fullscreen mode

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();
  });
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

The search bar in Weather & Clock Dashboard supports Google, Bing, DuckDuckGo, and Brave Search with a quick toggle.

firefox #javascript #ux #browserextension #webdev

Top comments (0)