Skip to content

Search result row

The row pattern for a search results page. Title + breadcrumb + snippet + matched-term highlighting.

'use client';
import type { ReactNode } from 'react';
import { Stack, Text } from '@helixui/core';
export interface SearchResult {
id: string;
title: string;
href: string;
breadcrumb: string[]; // ['Docs', 'Components', 'Button']
snippet: string; // raw text — highlightTerms wraps matches
matchedTerms?: string[];
}
function highlight(text: string, terms: string[] | undefined): ReactNode {
if (!terms || !terms.length) return text;
const re = new RegExp('(' + terms.map(escape).join('|') + ')', 'gi');
return text.split(re).map((part, i) =>
re.test(part) ? <mark key={i} style={{ background: 'var(--helixui-color-bg-action-brand-subtle)', borderRadius: 2 }}>{part}</mark> : part,
);
}
function escape(s: string) { return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); }
export function SearchResultRow({ result }: { result: SearchResult }) {
return (
<a
href={result.href}
style={{
display: 'block',
padding: 'var(--helixui-space-3) var(--helixui-space-4)',
borderRadius: 'var(--helixui-radius-md)',
textDecoration: 'none',
color: 'inherit',
}}
className="search-result-row"
>
<Stack gap={1}>
<Text size="xs" tone="muted">{result.breadcrumb.join(' / ')}</Text>
<Text weight="semibold" tone="primary">{highlight(result.title, result.matchedTerms)}</Text>
<Text size="sm" tone="secondary">{highlight(result.snippet, result.matchedTerms)}</Text>
</Stack>
</a>
);
}

Hover state

Add this once to your global stylesheet:

.search-result-row:hover {
background: var(--helixui-color-bg-surface-subtle);
}

Cross-cutting hover styles like this belong in CSS, not inline — they don’t compose well via style props.