Emergency Hotline: Call 1-844-363-1423 (United We Dream Hotline)
ICE Encounter

Search as Primary Navigation

At 223 pages, search transitions from a secondary fallback utility to a primary navigation vector. Implementing robust, lightning-fast static-site search is critical for both performance and complex discoverability.


Pagefind Configuration

Pagefind represents the optimal search architecture for large-scale static sites built on Eleventy. It operates entirely without backend infrastructure, indexing built HTML files and chunking the search index into smaller WebAssembly (WASM) fragments.

Performance Characteristics

Metric Capability
Index size < 300kB for 10,000 pages
Query speed Instant client-side
Bandwidth Minimal, ideal for 3G/prepaid
Infrastructure Zero server dependencies

Basic Integration

// eleventy.config.js
module.exports = function(eleventyConfig) {
  eleventyConfig.on('eleventy.after', async () => {
    const { createIndex } = await import('pagefind');
    const { index } = await createIndex();

    await index.addDirectory({
      path: '_site'
    });

    await index.writeFiles({
      outputPath: '_site/pagefind'
    });
  });
};

Indexing Strategy

Targeted Indexing

Extraneous DOM elements must be explicitly excluded from the index to prevent false positives and index bloat.

<!-- Exclude navigation from search index -->
<nav data-pagefind-ignore>
  <!-- Navigation content -->
</nav>

<!-- Exclude footer -->
<footer data-pagefind-ignore>
  <!-- Footer content -->
</footer>

<!-- Exclude "Related Links" sidebars -->
<aside class="related-links" data-pagefind-ignore>
  <!-- Related content -->
</aside>

Content Weighting

Pagefind uses a quadratic scaling system for content weighting. By default, <h1> elements carry a weight of 7.0.

<!-- Boost emergency protocols in crisis queries -->
<div data-pagefind-weight="10.0">
  <h2>If ICE is at Your Door</h2>
  <p>DO NOT OPEN THE DOOR. You have the right to remain silent.</p>
</div>

<!-- Boost legal deadlines -->
<div class="deadline-warning" data-pagefind-weight="8.0">
  <p>You have <strong>30 days</strong> to file your appeal.</p>
</div>

<!-- Standard content weight (default 1.0) -->
<p>Additional background information...</p>

Metadata Surfacing

Each page must define metadata for rich search result cards:

<head>
  <meta data-pagefind-meta="title" content="Workplace Raid Response Guide">
  <meta data-pagefind-meta="image" content="/images/workplace-rights.jpg">
  <meta data-pagefind-meta="image_alt" content="Worker rights illustration">
  <meta data-pagefind-meta="audience" content="General Public">
  <meta data-pagefind-meta="urgency" content="Emergency">
</head>

Filter Attributes

Enable faceted filtering via data attributes:

<!-- In page frontmatter/template -->
<body data-pagefind-filter-audience="General Public"
      data-pagefind-filter-type="Guide"
      data-pagefind-filter-topic="Workplace Enforcement">

Faceted Search Implementation

Faceted search enables users to narrow the 223-page corpus by taxonomy dimensions.

Client-Side Pill Toggles

<div class="search-facets">
  <fieldset class="facet-group">
    <legend>Audience</legend>
    <label class="facet-pill">
      <input type="checkbox" name="audience" value="general-public">
      <span>General Public</span>
    </label>
    <label class="facet-pill">
      <input type="checkbox" name="audience" value="attorneys">
      <span>Attorneys</span>
    </label>
    <label class="facet-pill">
      <input type="checkbox" name="audience" value="advocates">
      <span>Advocates</span>
    </label>
  </fieldset>

  <fieldset class="facet-group">
    <legend>Format</legend>
    <label class="facet-pill">
      <input type="checkbox" name="format" value="guide">
      <span>Guides</span>
    </label>
    <label class="facet-pill">
      <input type="checkbox" name="format" value="printable">
      <span>Printables</span>
    </label>
    <label class="facet-pill">
      <input type="checkbox" name="format" value="tool">
      <span>Tools</span>
    </label>
  </fieldset>
</div>
.facet-pill {
  display: inline-flex;
  align-items: center;
  padding: 0.5rem 1rem;
  border: 2px solid var(--color-border);
  border-radius: 2rem;
  cursor: pointer;
  transition: all 0.2s;
}

.facet-pill:has(:checked) {
  background: var(--color-primary);
  border-color: var(--color-primary);
  color: white;
}

.facet-pill input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

Pagefind Filter Integration

// Search with facets
const pagefind = await import('/pagefind/pagefind.js');
await pagefind.init();

async function performSearch(query, filters) {
  const results = await pagefind.search(query, {
    filters: {
      audience: filters.audience || [],
      type: filters.type || [],
      topic: filters.topic || []
    }
  });

  return results;
}

// Example: Search for "workplace" filtered to "Printables"
const results = await performSearch('workplace', {
  type: ['printable']
});

Search Results UX

Full-Screen Overlay

On mobile, search should expand into a full-screen overlay presenting both the input field and popular filter facets simultaneously.

<div class="search-overlay" hidden>
  <div class="search-overlay__header">
    <input type="search"
           class="search-overlay__input"
           placeholder="Search legal resources..."
           aria-label="Search">
    <button class="search-overlay__close" aria-label="Close search">×</button>
  </div>

  <div class="search-overlay__facets">
    <!-- Facet pills -->
  </div>

  <div class="search-overlay__results">
    <!-- Results populated via JS -->
  </div>
</div>
.search-overlay {
  position: fixed;
  inset: 0;
  background: var(--color-surface);
  z-index: 2000;
  display: flex;
  flex-direction: column;
}

.search-overlay__header {
  display: flex;
  padding: 1rem;
  gap: 1rem;
  border-bottom: 1px solid var(--color-border);
}

.search-overlay__input {
  flex: 1;
  padding: 1rem;
  font-size: 1.125rem;
  border: 2px solid var(--color-primary);
  border-radius: 8px;
}

.search-overlay__results {
  flex: 1;
  overflow-y: auto;
  padding: 1rem;
}

Result Card Display

<article class="search-result">
  <div class="search-result__meta">
    <span class="search-result__type">Guide</span>
    <span class="search-result__urgency search-result__urgency--emergency">
      Emergency
    </span>
  </div>
  <h3 class="search-result__title">
    <a href="/know-your-rights/workplace-raids/">
      Workplace Raid Response Guide
    </a>
  </h3>
  <p class="search-result__excerpt">
    ...you have the right to <mark>remain silent</mark> and refuse...
  </p>
  <div class="search-result__actions">
    <a href="/printables/workplace-card/" class="search-result__action">
      Download Printable
    </a>
  </div>
</article>

Zero-Result Fallbacks

When no results match, provide constructive alternatives:

<div class="search-no-results">
  <h2>No results found for "{{ query }}"</h2>

  <div class="no-results__suggestions">
    <h3>Try these instead:</h3>
    <ul>
      <li><a href="/know-your-rights/">Browse Know Your Rights guides</a></li>
      <li><a href="/printables/">View all printable resources</a></li>
      <li><a href="/emergency/">Emergency help</a></li>
    </ul>
  </div>

  <div class="no-results__popular">
    <h3>Popular searches:</h3>
    <ul>
      <li><a href="?q=ice+at+door">ICE at my door</a></li>
      <li><a href="?q=workplace+raid">Workplace raid</a></li>
      <li><a href="?q=daca+renewal">DACA renewal</a></li>
    </ul>
  </div>
</div>

Legal Term Normalization

Map colloquial terms to formal legal terminology:

// pagefind.config.js
export default {
  processTerm: (term) => {
    const synonyms = {
      'deportation': 'removal',
      'deported': 'removed',
      'green card': 'lawful permanent resident',
      'papers': 'documentation',
      'la migra': 'ice',
      'inmigración': 'immigration',
      'abogado': 'attorney',
      'corte': 'court',
      'fianza': 'bond'
    };
    return synonyms[term.toLowerCase()] || term;
  }
};

Autocomplete Suggestions

Populate search with high-volume problem-area queries:

const topSearches = [
  'ice at my door',
  'workplace raid rights',
  'checkpoint refuse search',
  'daca renewal',
  'find immigration lawyer',
  'red card',
  'detention visitation'
];

const searchInput = document.querySelector('.search-input');

searchInput.addEventListener('focus', () => {
  showSuggestions(topSearches);
});

searchInput.addEventListener('input', debounce(async (e) => {
  const query = e.target.value;

  if (query.length < 2) {
    showSuggestions(topSearches);
    return;
  }

  const results = await pagefind.search(query);
  showResults(results);
}, 200));

Search Analytics (Privacy-Preserving)

Track search behavior without compromising user privacy:

// Log search queries without PII
function logSearch(query, resultCount) {
  // Hash the query to prevent storing actual search terms
  const queryHash = hashString(query);

  // Only log aggregate patterns
  if (resultCount === 0) {
    incrementCounter('zero_result_searches');
  }

  // Track category clicks, not individual behavior
  incrementCounter(`search_category_${resultCount > 0 ? 'found' : 'empty'}`);
}

Testing Checklist

Search Functionality

  • [ ] Pagefind index builds without errors
  • [ ] Navigation elements excluded from index
  • [ ] Emergency content weighted higher
  • [ ] Faceted filters work correctly
  • [ ] Zero-result fallbacks display
  • [ ] Autocomplete suggestions appear

Performance

  • [ ] Index loads < 300kB
  • [ ] Query results < 100ms
  • [ ] Works on 3G connection
  • [ ] Mobile overlay smooth

Accessibility

  • [ ] Search accessible via keyboard
  • [ ] Screen reader announces results
  • [ ] Focus management correct
  • [ ] ARIA labels complete

Related Resources