From 4e44b558e10c7063566adb2dec2ca00191cccdbb Mon Sep 17 00:00:00 2001
From: Paul Jankowski <8bittitan@gmail.com>
Date: Wed, 5 Aug 2026 10:18:58 -0400
Subject: [PATCH] fix(v5): stop truncating mobile snippets (#2958)
* fix(v5): stop truncating mobile snippets
Backport of #2907.\n\nOriginal commit: 9ad6d169fee62cb9558e40aa8475f99ecd41e7fe
* fix(v5): allow mobile hit text to wrap
Completes the v5 adaptation of #2907 by overriding later v5 child-level truncation rules.\n\nOriginal commit: 9ad6d169fee62cb9558e40aa8475f99ecd41e7fe
* chore(v5): account for mobile wrapping CSS
Updates the CSS size budget for the v5 adaptation of #2907.\n\nOriginal commit: 9ad6d169fee62cb9558e40aa8475f99ecd41e7fe
---------
Co-authored-by: Divyansh Singh <40380293+brc-dd@users.noreply.github.com>
---
.changeset/long-snippets-wrap.md | 6 ++
bundlesize.config.json | 2 +-
packages/docsearch-css/src/modal.css | 16 ++++
.../src/DocSearchAskAiModal.tsx | 12 +--
.../docsearch-react/src/DocSearchModal.tsx | 12 +--
packages/docsearch-react/src/constants.ts | 1 +
.../src/hooks/__tests__/modal.test.tsx | 1 -
.../src/hooks/useDocSearchModalEffects.ts | 8 --
.../src/hooks/useModalEnvironment.ts | 4 +-
.../docsearch-react/src/hooks/useModalRefs.ts | 3 -
.../__tests__/createDocSearchSources.test.ts | 86 +++++++++++--------
.../src/utils/createDocSearchSources.ts | 17 ++--
12 files changed, 88 insertions(+), 80 deletions(-)
create mode 100644 .changeset/long-snippets-wrap.md
diff --git a/.changeset/long-snippets-wrap.md b/.changeset/long-snippets-wrap.md
new file mode 100644
index 00000000..28ece56d
--- /dev/null
+++ b/.changeset/long-snippets-wrap.md
@@ -0,0 +1,6 @@
+---
+"@docsearch/react": patch
+"@docsearch/css": patch
+---
+
+Stop over-truncating mobile snippets and allow long search hits to wrap.
diff --git a/bundlesize.config.json b/bundlesize.config.json
index 402f132a..3b4562c1 100644
--- a/bundlesize.config.json
+++ b/bundlesize.config.json
@@ -2,7 +2,7 @@
"files": [
{
"path": "packages/docsearch-css/dist/style.css",
- "maxSize": "8.05 kB"
+ "maxSize": "8.1 kB"
},
{
"path": "packages/docsearch-react/dist/umd/index.js",
diff --git a/packages/docsearch-css/src/modal.css b/packages/docsearch-css/src/modal.css
index 040f38bf..5d3fea32 100644
--- a/packages/docsearch-css/src/modal.css
+++ b/packages/docsearch-css/src/modal.css
@@ -1135,10 +1135,26 @@ assistive tech users */
.DocSearch-Hit-content-wrapper {
display: flex;
+ overflow-wrap: break-word;
position: relative;
+ white-space: normal;
width: 80%;
}
+ .DocSearch-Hit-title,
+ .DocSearch-Hit-path {
+ max-width: 100%;
+ overflow: visible;
+ text-overflow: clip;
+ white-space: normal;
+ }
+
+ .DocSearch-Hit-Container {
+ height: auto;
+ min-height: var(--docsearch-hit-height);
+ padding-block: 8px;
+ }
+
.DocSearch-Modal {
border-radius: 0;
box-shadow: none;
diff --git a/packages/docsearch-react/src/DocSearchAskAiModal.tsx b/packages/docsearch-react/src/DocSearchAskAiModal.tsx
index 10090858..999d0754 100644
--- a/packages/docsearch-react/src/DocSearchAskAiModal.tsx
+++ b/packages/docsearch-react/src/DocSearchAskAiModal.tsx
@@ -142,14 +142,8 @@ export function DocSearchAskAiModal({
'Ask another question...';
}
- const {
- containerRef,
- modalRef,
- formElementRef,
- dropdownRef,
- inputRef,
- snippetLength,
- } = useModalRefs();
+ const { containerRef, modalRef, formElementRef, dropdownRef, inputRef } =
+ useModalRefs();
const { initialQuery, initialQueryFromSelection } =
useInitialModalQuery(initialQueryFromProp);
@@ -417,7 +411,6 @@ export function DocSearchAskAiModal({
setStatus,
searchClient,
indexes,
- snippetLength,
insights: Boolean(insights),
appId,
apiKey,
@@ -460,7 +453,6 @@ export function DocSearchAskAiModal({
inputRef,
initialScrollY,
modalRef,
- snippetLength,
theme,
});
diff --git a/packages/docsearch-react/src/DocSearchModal.tsx b/packages/docsearch-react/src/DocSearchModal.tsx
index 6299b7e6..b74482e7 100644
--- a/packages/docsearch-react/src/DocSearchModal.tsx
+++ b/packages/docsearch-react/src/DocSearchModal.tsx
@@ -95,14 +95,8 @@ export function DocSearchModal({
props.placeholder ||
'Search docs';
- const {
- containerRef,
- modalRef,
- formElementRef,
- dropdownRef,
- inputRef,
- snippetLength,
- } = useModalRefs();
+ const { containerRef, modalRef, formElementRef, dropdownRef, inputRef } =
+ useModalRefs();
const { initialQuery, initialQueryFromSelection } =
useInitialModalQuery(initialQueryFromProp);
@@ -193,7 +187,6 @@ export function DocSearchModal({
setStatus,
searchClient,
indexes,
- snippetLength,
insights: Boolean(insights),
appId,
apiKey,
@@ -221,7 +214,6 @@ export function DocSearchModal({
inputRef,
initialScrollY,
modalRef,
- snippetLength,
theme,
});
diff --git a/packages/docsearch-react/src/constants.ts b/packages/docsearch-react/src/constants.ts
index 3fb3702c..212fc789 100644
--- a/packages/docsearch-react/src/constants.ts
+++ b/packages/docsearch-react/src/constants.ts
@@ -1,3 +1,4 @@
export const MAX_QUERY_SIZE = 512;
+export const SNIPPET_LENGTH = 15;
export const SUGGESTED_QUETIONS_INDEX_NAME =
'algolia_ask_ai_suggested_questions';
diff --git a/packages/docsearch-react/src/hooks/__tests__/modal.test.tsx b/packages/docsearch-react/src/hooks/__tests__/modal.test.tsx
index 7b70e9ee..0d922d8c 100644
--- a/packages/docsearch-react/src/hooks/__tests__/modal.test.tsx
+++ b/packages/docsearch-react/src/hooks/__tests__/modal.test.tsx
@@ -117,7 +117,6 @@ describe('modal hooks', () => {
expect(firstResult.formElementRef.current).toBeNull();
expect(firstResult.dropdownRef.current).toBeNull();
expect(firstResult.inputRef.current).toBeNull();
- expect(firstResult.snippetLength.current).toBe(15);
rerender();
diff --git a/packages/docsearch-react/src/hooks/useDocSearchModalEffects.ts b/packages/docsearch-react/src/hooks/useDocSearchModalEffects.ts
index e0e92f83..8e17991b 100644
--- a/packages/docsearch-react/src/hooks/useDocSearchModalEffects.ts
+++ b/packages/docsearch-react/src/hooks/useDocSearchModalEffects.ts
@@ -7,12 +7,10 @@ import { manageLocalStorageQuota } from '../utils/storage';
export function useDocSearchModalEffects({
initialScrollY,
modalRef,
- snippetLength,
theme,
}: {
initialScrollY: number;
modalRef: React.RefObject;
- snippetLength: React.MutableRefObject;
theme?: DocSearchTheme;
}): void {
useTheme({ theme });
@@ -39,12 +37,6 @@ export function useDocSearchModalEffects({
};
}, []);
- React.useEffect(() => {
- if (window.matchMedia('(max-width: 768px)').matches) {
- snippetLength.current = 5;
- }
- }, [snippetLength]);
-
React.useEffect(() => {
function setFullViewportHeight(): void {
if (modalRef.current) {
diff --git a/packages/docsearch-react/src/hooks/useModalEnvironment.ts b/packages/docsearch-react/src/hooks/useModalEnvironment.ts
index 3ad094a9..e5de889f 100644
--- a/packages/docsearch-react/src/hooks/useModalEnvironment.ts
+++ b/packages/docsearch-react/src/hooks/useModalEnvironment.ts
@@ -15,7 +15,6 @@ export function useModalEnvironment({
inputRef,
initialScrollY,
modalRef,
- snippetLength,
theme,
}: {
getEnvironmentProps: AutocompleteApi<
@@ -30,7 +29,6 @@ export function useModalEnvironment({
inputRef: React.RefObject;
initialScrollY: number;
modalRef: React.RefObject;
- snippetLength: React.MutableRefObject;
theme?: DocSearchTheme;
}): void {
useTouchEvents({
@@ -40,5 +38,5 @@ export function useModalEnvironment({
inputElement: inputRef.current,
});
useTrapFocus({ container: containerRef.current });
- useDocSearchModalEffects({ initialScrollY, modalRef, snippetLength, theme });
+ useDocSearchModalEffects({ initialScrollY, modalRef, theme });
}
diff --git a/packages/docsearch-react/src/hooks/useModalRefs.ts b/packages/docsearch-react/src/hooks/useModalRefs.ts
index ed3ebe67..ccc7c7c3 100644
--- a/packages/docsearch-react/src/hooks/useModalRefs.ts
+++ b/packages/docsearch-react/src/hooks/useModalRefs.ts
@@ -6,14 +6,12 @@ export function useModalRefs(): {
formElementRef: React.RefObject;
dropdownRef: React.RefObject;
inputRef: React.RefObject;
- snippetLength: React.MutableRefObject;
} {
const containerRef = React.useRef(null);
const modalRef = React.useRef(null);
const formElementRef = React.useRef(null);
const dropdownRef = React.useRef(null);
const inputRef = React.useRef(null);
- const snippetLength = React.useRef(15);
return {
containerRef,
@@ -21,6 +19,5 @@ export function useModalRefs(): {
formElementRef,
dropdownRef,
inputRef,
- snippetLength,
};
}
diff --git a/packages/docsearch-react/src/utils/__tests__/createDocSearchSources.test.ts b/packages/docsearch-react/src/utils/__tests__/createDocSearchSources.test.ts
index 57860388..45b7f173 100644
--- a/packages/docsearch-react/src/utils/__tests__/createDocSearchSources.test.ts
+++ b/packages/docsearch-react/src/utils/__tests__/createDocSearchSources.test.ts
@@ -63,55 +63,71 @@ function createHit({
describe('buildQuerySources', () => {
it('creates a source per lvl0 group and scopes parents to that group', async () => {
+ const search = vi.fn().mockResolvedValue({
+ results: [
+ {
+ index: 'docs',
+ nbHits: 4,
+ hits: [
+ createHit({
+ objectID: 'guide-install',
+ lvl0: 'Guides',
+ lvl1: 'Installation',
+ type: 'lvl1',
+ }),
+ createHit({
+ objectID: 'guide-install-content',
+ lvl0: 'Guides',
+ lvl1: 'Installation',
+ type: 'content',
+ }),
+ createHit({
+ objectID: 'reference-install',
+ lvl0: 'Reference',
+ lvl1: 'Installation',
+ type: 'lvl1',
+ }),
+ createHit({
+ objectID: 'reference-install-content',
+ lvl0: 'Reference',
+ lvl1: 'Installation',
+ type: 'content',
+ }),
+ ],
+ },
+ ],
+ });
const sources = await buildQuerySources({
query: 'install',
state: { context: { searchSuggestions: [] } },
setContext: vi.fn(),
setStatus: vi.fn(),
searchClient: {
- search: vi.fn().mockResolvedValue({
- results: [
- {
- index: 'docs',
- nbHits: 4,
- hits: [
- createHit({
- objectID: 'guide-install',
- lvl0: 'Guides',
- lvl1: 'Installation',
- type: 'lvl1',
- }),
- createHit({
- objectID: 'guide-install-content',
- lvl0: 'Guides',
- lvl1: 'Installation',
- type: 'content',
- }),
- createHit({
- objectID: 'reference-install',
- lvl0: 'Reference',
- lvl1: 'Installation',
- type: 'lvl1',
- }),
- createHit({
- objectID: 'reference-install-content',
- lvl0: 'Reference',
- lvl1: 'Installation',
- type: 'content',
- }),
- ],
- },
- ],
- }),
+ search,
} as any,
indexes: [{ name: 'docs' }],
- snippetLength: { current: 15 },
insights: false,
saveRecentSearch: vi.fn(),
onClose: vi.fn(),
facetSelections: { current: {} },
});
+ expect(search).toHaveBeenCalledWith({
+ requests: [
+ expect.objectContaining({
+ attributesToSnippet: [
+ 'hierarchy.lvl1:15',
+ 'hierarchy.lvl2:15',
+ 'hierarchy.lvl3:15',
+ 'hierarchy.lvl4:15',
+ 'hierarchy.lvl5:15',
+ 'hierarchy.lvl6:15',
+ 'content:15',
+ ],
+ }),
+ ],
+ });
+
expect(sources.map((source) => source.sourceId)).toEqual([
'hits_docs_0',
'hits_docs_1',
diff --git a/packages/docsearch-react/src/utils/createDocSearchSources.ts b/packages/docsearch-react/src/utils/createDocSearchSources.ts
index 428bcb48..7d5dc226 100644
--- a/packages/docsearch-react/src/utils/createDocSearchSources.ts
+++ b/packages/docsearch-react/src/utils/createDocSearchSources.ts
@@ -9,6 +9,7 @@ import type {
} from 'algoliasearch/lite';
import type React from 'react';
+import { SNIPPET_LENGTH } from '../constants';
import type { DocSearchIndex, DocSearchProps } from '../DocSearch';
import type {
DocSearchHit,
@@ -135,7 +136,6 @@ export async function buildQuerySources({
setStatus,
searchClient,
indexes,
- snippetLength,
insights,
appId,
apiKey,
@@ -153,7 +153,6 @@ export async function buildQuerySources({
setStatus: (status: DocSearchState['status']) => void;
searchClient: ReturnType;
indexes: DocSearchIndex[];
- snippetLength: React.MutableRefObject;
insights: boolean;
appId?: string;
apiKey?: string;
@@ -191,13 +190,13 @@ export async function buildQuerySources({
'url',
],
attributesToSnippet: searchParams?.attributesToSnippet ?? [
- `hierarchy.lvl1:${snippetLength.current}`,
- `hierarchy.lvl2:${snippetLength.current}`,
- `hierarchy.lvl3:${snippetLength.current}`,
- `hierarchy.lvl4:${snippetLength.current}`,
- `hierarchy.lvl5:${snippetLength.current}`,
- `hierarchy.lvl6:${snippetLength.current}`,
- `content:${snippetLength.current}`,
+ `hierarchy.lvl1:${SNIPPET_LENGTH}`,
+ `hierarchy.lvl2:${SNIPPET_LENGTH}`,
+ `hierarchy.lvl3:${SNIPPET_LENGTH}`,
+ `hierarchy.lvl4:${SNIPPET_LENGTH}`,
+ `hierarchy.lvl5:${SNIPPET_LENGTH}`,
+ `hierarchy.lvl6:${SNIPPET_LENGTH}`,
+ `content:${SNIPPET_LENGTH}`,
],
snippetEllipsisText: searchParams?.snippetEllipsisText ?? '…',
highlightPreTag: searchParams?.highlightPreTag ?? '',