diff --git a/apps/app/src/app/(app)/[orgId]/frameworks/[frameworkInstanceId]/components/ExpandableDescription.test.tsx b/apps/app/src/app/(app)/[orgId]/frameworks/[frameworkInstanceId]/components/ExpandableDescription.test.tsx
new file mode 100644
index 0000000000..f567857b62
--- /dev/null
+++ b/apps/app/src/app/(app)/[orgId]/frameworks/[frameworkInstanceId]/components/ExpandableDescription.test.tsx
@@ -0,0 +1,41 @@
+import { fireEvent, render, screen, within } from '@testing-library/react';
+import { describe, expect, it, vi } from 'vitest';
+import { ExpandableDescription } from './ExpandableDescription';
+
+const LONG =
+ 'Develop security and privacy plans for the system that are consistent with the enterprise architecture.';
+
+describe('ExpandableDescription', () => {
+ it('renders the description inline with a read-more affordance', () => {
+ render();
+ expect(screen.getByText(LONG)).toBeInTheDocument();
+ expect(
+ screen.getByRole('button', { name: /read full description/i }),
+ ).toBeInTheDocument();
+ });
+
+ it('opens a dialog with the full description and an identifier · name heading', () => {
+ render();
+ fireEvent.click(screen.getByRole('button', { name: /read full description/i }));
+ const dialog = screen.getByRole('dialog');
+ expect(within(dialog).getByText('PL-2 · System Security')).toBeInTheDocument();
+ expect(within(dialog).getByText(LONG)).toBeInTheDocument();
+ });
+
+ it('renders an em dash and no button when there is no description', () => {
+ render();
+ expect(screen.getByText('—')).toBeInTheDocument();
+ expect(screen.queryByRole('button', { name: /read full description/i })).toBeNull();
+ });
+
+ it('does not trigger the clickable parent row when expanding', () => {
+ const onRowClick = vi.fn();
+ render(
+
+
+
,
+ );
+ fireEvent.click(screen.getByRole('button', { name: /read full description/i }));
+ expect(onRowClick).not.toHaveBeenCalled();
+ });
+});
diff --git a/apps/app/src/app/(app)/[orgId]/frameworks/[frameworkInstanceId]/components/ExpandableDescription.tsx b/apps/app/src/app/(app)/[orgId]/frameworks/[frameworkInstanceId]/components/ExpandableDescription.tsx
new file mode 100644
index 0000000000..57f5a67dee
--- /dev/null
+++ b/apps/app/src/app/(app)/[orgId]/frameworks/[frameworkInstanceId]/components/ExpandableDescription.tsx
@@ -0,0 +1,69 @@
+'use client';
+
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+} from '@trycompai/design-system';
+import { Maximize } from '@trycompai/design-system/icons';
+import { useState } from 'react';
+
+interface ExpandableDescriptionProps {
+ description: string | null | undefined;
+ identifier?: string | null;
+ name?: string | null;
+}
+
+/**
+ * Read-only requirement description cell. Shows the truncated text inline and,
+ * on hover, a maximize button that opens a dialog with the full description —
+ * long framework requirements (e.g. NIST SP800-53 PL-2) are otherwise
+ * unreadable behind the single-line truncation + native tooltip.
+ */
+export function ExpandableDescription({
+ description,
+ identifier,
+ name,
+}: ExpandableDescriptionProps) {
+ const [isOpen, setIsOpen] = useState(false);
+
+ if (!description) {
+ return —;
+ }
+
+ const heading = [identifier?.trim(), name].filter(Boolean).join(' · ') || 'Requirement';
+
+ return (
+