diff --git a/.changeset/hook-doc-use-truncation.md b/.changeset/hook-doc-use-truncation.md
new file mode 100644
index 0000000000000..f38f5f64a1635
--- /dev/null
+++ b/.changeset/hook-doc-use-truncation.md
@@ -0,0 +1,8 @@
+---
+'@astryxdesign/core': patch
+'@astryxdesign/cli': patch
+---
+
+[docs] Add hook documentation and interactive table cell truncation example block for `useTruncation`, demonstrating responsive string truncation and accessibility tooltips.
+
+@utkarshpatwa393-sys
diff --git a/apps/sandbox/scripts/next-export-routes.json b/apps/sandbox/scripts/next-export-routes.json
index 32b6875b13fb2..990fe6f6cf767 100644
--- a/apps/sandbox/scripts/next-export-routes.json
+++ b/apps/sandbox/scripts/next-export-routes.json
@@ -843,6 +843,7 @@
"/templates/useToastHookUsage/",
"/templates/useTranslatorHookUsage/",
"/templates/useTreeFocusHookUsage/",
+ "/templates/useTruncationHookUsage/",
"/templates/useTypeaheadHookUsage/",
"/templates/work-item-detail/",
"/themes/",
diff --git a/internal/shadcn-registry/routes.lock.json b/internal/shadcn-registry/routes.lock.json
index a8bea310e3c09..4fd0cf1d31f01 100644
--- a/internal/shadcn-registry/routes.lock.json
+++ b/internal/shadcn-registry/routes.lock.json
@@ -4036,6 +4036,11 @@
"path": "examples/use-tree-focus/keyboard-file-tree",
"aliases": []
},
+ {
+ "name": "example-use-truncation-tooltip-on-cut-off-text",
+ "path": "examples/use-truncation/tooltip-on-cut-off-text",
+ "aliases": []
+ },
{
"name": "example-use-typeahead-type-to-focus-a-country",
"path": "examples/use-typeahead/type-to-focus-a-country",
@@ -4466,6 +4471,11 @@
"path": "hooks/use-tree-focus",
"aliases": []
},
+ {
+ "name": "hook-use-truncation",
+ "path": "hooks/use-truncation",
+ "aliases": []
+ },
{
"name": "hook-use-typeahead",
"path": "hooks/use-typeahead",
diff --git a/packages/cli/api/component/__fixtures__/resolutions/modules.txt b/packages/cli/api/component/__fixtures__/resolutions/modules.txt
index c638525ff0978..71c7af2095fb1 100644
--- a/packages/cli/api/component/__fixtures__/resolutions/modules.txt
+++ b/packages/cli/api/component/__fixtures__/resolutions/modules.txt
@@ -303,5 +303,5 @@ useTableSortableState useTableSortableState Table/plugins/sortable/useTableSorta
useToastGesture useToastGesture Toast/Toast.doc.mjs Toast/useToastGesture.ts
useTouchTrigger useTouchTrigger Layer/Layer.doc.mjs Layer/useTouchTrigger.ts
useTriggerMenu useTriggerMenu Chat/Chat.doc.mjs Chat/useTriggerMenu.tsx
-useTruncation useTruncation Text/Heading.doc.mjs Text/useTruncation.ts
+useTruncation useTruncation Text/useTruncation.doc.mjs Text/useTruncation.ts
utils utils Calendar/Calendar.doc.mjs Calendar/utils.ts
diff --git a/packages/cli/api/template/use-truncation-hook-usage.test.tsx b/packages/cli/api/template/use-truncation-hook-usage.test.tsx
new file mode 100644
index 0000000000000..14751aad928e1
--- /dev/null
+++ b/packages/cli/api/template/use-truncation-hook-usage.test.tsx
@@ -0,0 +1,76 @@
+// Copyright (c) Meta Platforms, Inc. and affiliates.
+// @vitest-environment jsdom
+
+/**
+ * The useTruncation example demonstrates text truncation across table cells
+ * with single-line and multi-line clamps.
+ */
+
+import {act} from 'react';
+import {createRoot, type Root} from 'react-dom/client';
+import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest';
+import UseTruncationHookUsage from '../../assets/templates/blocks/components/Text/useTruncationHookUsage';
+
+(globalThis as {IS_REACT_ACT_ENVIRONMENT?: boolean}).IS_REACT_ACT_ENVIRONMENT =
+ true;
+
+let root: Root | null = null;
+let container: HTMLElement;
+
+beforeEach(() => {
+ vi.stubGlobal(
+ 'matchMedia',
+ (query: string) =>
+ ({
+ matches: false,
+ media: query,
+ onchange: null,
+ addListener: () => {},
+ removeListener: () => {},
+ addEventListener: () => {},
+ removeEventListener: () => {},
+ dispatchEvent: () => false,
+ }) as unknown as MediaQueryList,
+ );
+ container = document.createElement('div');
+ document.body.appendChild(container);
+});
+
+afterEach(() => {
+ act(() => root?.unmount());
+ root = null;
+ container.remove();
+ vi.unstubAllGlobals();
+});
+
+describe('useTruncationHookUsage example', () => {
+ it('renders the narrow path column with the fixture rows and clamped cells', () => {
+ root = createRoot(container);
+ act(() => root!.render());
+
+ const cells = Array.from(container.querySelectorAll('td')).map(
+ cell => cell.textContent ?? '',
+ );
+ expect(cells).toContain('build');
+ expect(cells).toContain('bundle');
+ expect(cells).toContain('readme');
+
+ // The paths reach the DOM
+ expect(container.textContent).toContain('build-20261103-120344');
+ expect(container.textContent).toContain('reports/coverage');
+ expect(container.textContent).toContain('README.md');
+ });
+
+ it('renders measured text elements across all table cells', async () => {
+ root = createRoot(container);
+ act(() => {
+ root!.render();
+ });
+
+ await act(async () => {});
+
+ // In the DOM, cells containing paths render span elements with truncation refs
+ const spans = Array.from(container.querySelectorAll('td span'));
+ expect(spans.length).toBe(3);
+ });
+});
diff --git a/packages/cli/assets/templates/blocks/components/Text/useTruncationHookUsage.doc.mjs b/packages/cli/assets/templates/blocks/components/Text/useTruncationHookUsage.doc.mjs
new file mode 100644
index 0000000000000..4dde32bf99a98
--- /dev/null
+++ b/packages/cli/assets/templates/blocks/components/Text/useTruncationHookUsage.doc.mjs
@@ -0,0 +1,14 @@
+// Copyright (c) Meta Platforms, Inc. and affiliates.
+
+/** @type {import('@astryxdesign/cli/authoring').TemplateDoc} */
+export const doc = {
+ type: 'block',
+ exampleFor: 'useTruncation',
+ name: 'useTruncation — Tooltip on Cut-Off Text',
+ displayName: 'useTruncation — Tooltip on Cut-Off Text',
+ description:
+ 'A narrow table column shows the full path in a Tooltip only where useTruncation reports the text is cut off; the short path shows none. One cell clamped to two lines shows the multi-line case.',
+ isReady: true,
+ aspectRatio: 4 / 3,
+ componentsUsed: ['Table', 'Tooltip'],
+};
diff --git a/packages/cli/assets/templates/blocks/components/Text/useTruncationHookUsage.tsx b/packages/cli/assets/templates/blocks/components/Text/useTruncationHookUsage.tsx
new file mode 100644
index 0000000000000..19b0d46b3824f
--- /dev/null
+++ b/packages/cli/assets/templates/blocks/components/Text/useTruncationHookUsage.tsx
@@ -0,0 +1,112 @@
+// Copyright (c) Meta Platforms, Inc. and affiliates.
+
+'use client';
+
+import {useTruncation} from '@astryxdesign/core/Text';
+import {Tooltip} from '@astryxdesign/core/Tooltip';
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHeader,
+ TableHeaderCell,
+ TableRow,
+} from '@astryxdesign/core/Table';
+
+interface FileAsset {
+ name: string;
+ path: string;
+ mode: 'single' | 'clamped' | 'short';
+}
+
+const FILES: FileAsset[] = [
+ {
+ name: 'build',
+ path: 'artifacts/release/build-20261103-120344/index.html',
+ mode: 'single',
+ },
+ {
+ name: 'bundle',
+ path: 'reports/coverage/packages/core/src/components/Calendar/index.html',
+ mode: 'clamped',
+ },
+ {
+ name: 'readme',
+ path: 'README.md',
+ mode: 'short',
+ },
+];
+
+function TruncatedCell({children}: {children: string}) {
+ const truncation = useTruncation({maxLines: 1});
+ return (
+
+
+ {children}
+
+
+ );
+}
+
+function ClampedCell({children}: {children: string}) {
+ const truncation = useTruncation({maxLines: 2});
+ return (
+
+
+ {children}
+
+
+ );
+}
+
+export default function UseTruncationHookUsage() {
+ return (
+
+
+
+ Asset
+ Path
+
+
+
+ {FILES.map(file => (
+
+ {file.name}
+
+ {file.mode === 'clamped' ? (
+ {file.path}
+ ) : (
+ {file.path}
+ )}
+
+
+ ))}
+
+
+ );
+}
diff --git a/packages/core/src/Text/useTruncation.doc.mjs b/packages/core/src/Text/useTruncation.doc.mjs
new file mode 100644
index 0000000000000..d5eb9c9e37600
--- /dev/null
+++ b/packages/core/src/Text/useTruncation.doc.mjs
@@ -0,0 +1,127 @@
+// Copyright (c) Meta Platforms, Inc. and affiliates.
+
+/** @type {import('@astryxdesign/cli/authoring').HookDoc} */
+export const docs = {
+ name: 'useTruncation',
+ displayName: 'useTruncation',
+ group: 'Text',
+ keywords: [
+ 'truncation',
+ 'truncate',
+ 'ellipsis',
+ 'overflow',
+ 'line-clamp',
+ 'clamp',
+ 'tooltip',
+ 'full text',
+ 'text cut off',
+ 'table cell',
+ ],
+ params: [
+ {
+ name: 'options',
+ type: 'UseTruncationOptions',
+ description: 'Configuration object.',
+ required: true,
+ },
+ {
+ name: 'options.maxLines',
+ type: 'number',
+ description:
+ 'Maximum number of lines before truncation. 1 detects single-line overflow (scrollWidth against offsetWidth); values above 1 detect a -webkit-line-clamp cut by measuring the real content height with a Range, bypassing the clamped scrollHeight. 0 disables detection: isTruncated stays false and fullText stays empty.',
+ required: true,
+ },
+ ],
+ returns: [
+ {
+ name: 'ref',
+ type: 'RefCallback',
+ description:
+ 'Ref callback to attach to the text element being measured. Re-attaching to another element re-observes it and cleans up the previous observation.',
+ },
+ {
+ name: 'isTruncated',
+ type: 'boolean',
+ description:
+ 'Whether the text is currently cut off. Drives composing a Tooltip with the full text: render the tooltip only when this is true, so a cell that fits shows none.',
+ },
+ {
+ name: 'fullText',
+ type: 'string',
+ description:
+ "The element's current text content, captured on every measurement, for the tooltip a truncated cell shows. Follows content changes that resize nothing (a fixed-width status line whose text swaps).",
+ },
+ ],
+ usage: {
+ description:
+ "Detects whether an element's text is cut off — single-line overflow or clamped to several lines — and gives back the full text, updating when the size or the content changes. Built on a shared ResizeObserver and a shared MutationObserver, so hundreds of table cells create one observer of each kind, and a first measurement runs batched in a microtask after the commit instead of forcing a layout pass per text during mount. Use it wherever a truncated cell should show its full content only when it is actually truncated: compose a Tooltip off isTruncated and fullText instead of always rendering one. Text and Heading build their truncation tooltips on it; reach for it directly when composing truncation feedback into your own component.",
+ bestPractices: [
+ {
+ guidance: true,
+ description:
+ 'Render the full-content Tooltip only when isTruncated is true, and read the tooltip text from fullText: a cell that fits shows no tooltip, and the text always matches what was measured.',
+ },
+ {
+ guidance: true,
+ description:
+ 'Apply the matching clamp yourself next to the hook — white-space: nowrap for maxLines 1, -webkit-line-clamp for more — so what the user sees and what the hook measures stay the same shape.',
+ },
+ {
+ guidance: true,
+ description:
+ 'Reuse one useTruncation instance per measured element; the ref callback cleans up its own observers, so swapping elements is safe.',
+ },
+ {
+ guidance: false,
+ description:
+ 'Compare scrollHeight to offsetHeight yourself for clamped text: with -webkit-line-clamp active browsers report the clamped size, which is exactly the case the Range measurement exists for.',
+ },
+ ],
+ },
+ relatedComponents: ['Text', 'Heading', 'Tooltip'],
+ importPath: '@astryxdesign/core/Text',
+ category: 'text',
+};
+
+/** @type {import('@astryxdesign/cli/authoring').HookTranslationDoc} */
+export const docsDense = {
+ description:
+ 'Detects whether text is cut off (single-line overflow or -webkit-line-clamp) and returns the full text, updating on size and content changes. Shared Resize/Mutation observers keep hundreds of cells at one observer each; first measurement batches in a microtask after commit.',
+ paramDescriptions: {
+ options: 'config object.',
+ 'options.maxLines':
+ 'lines allowed before truncation; 1 = single-line overflow check, >1 = clamp detection via Range measurement, 0 = off.',
+ },
+ returnDescriptions: {
+ ref: 'ref callback for the measured text element; swapping elements re-observes and cleans up.',
+ isTruncated:
+ 'true while the text is cut off; gate the full-content Tooltip on it.',
+ fullText:
+ 'current text content for the tooltip; follows content changes that resize nothing.',
+ },
+ usage: {
+ description:
+ 'Truncation detection + full text for tooltips. Gate a Tooltip on isTruncated and read its text from fullText so only actually-cut cells show one. Shared observers; batched first measurement. Text/Heading build their truncation tooltips on it; use directly when composing truncation feedback yourself.',
+ bestPractices: [
+ {
+ guidance: true,
+ description: 'Tooltip only when isTruncated; text from fullText.',
+ },
+ {
+ guidance: true,
+ description:
+ 'Apply the matching clamp (nowrap / line-clamp) next to the hook so view and measurement agree.',
+ },
+ {
+ guidance: true,
+ description:
+ 'One hook instance per measured element; the ref cleans up after itself.',
+ },
+ {
+ guidance: false,
+ description:
+ 'Manual scrollHeight checks on clamped text — browsers report the clamped size.',
+ },
+ ],
+ },
+};