diff --git a/src/design-system/composites/DetailPanel/DetailPanel.module.css b/src/design-system/composites/DetailPanel/DetailPanel.module.css
new file mode 100644
index 0000000..ef08847
--- /dev/null
+++ b/src/design-system/composites/DetailPanel/DetailPanel.module.css
@@ -0,0 +1,108 @@
+.panel {
+ width: 0;
+ overflow: hidden;
+ transition: width 0.25s ease, opacity 0.2s ease;
+ opacity: 0;
+ border-left: 1px solid transparent;
+ display: flex;
+ flex-direction: column;
+ background: var(--bg-surface);
+ flex-shrink: 0;
+}
+
+.panel.open {
+ width: 400px;
+ opacity: 1;
+ border-left-color: var(--border);
+ animation: slideInRight 0.25s ease-out both;
+}
+
+@keyframes slideInRight {
+ from { opacity: 0; transform: translateX(20px); }
+ to { opacity: 1; transform: translateX(0); }
+}
+
+.header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 12px 16px;
+ border-bottom: 1px solid var(--border-subtle);
+ flex-shrink: 0;
+}
+
+.title {
+ font-size: 14px;
+ font-weight: 600;
+ color: var(--text-primary);
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.closeBtn {
+ width: 26px;
+ height: 26px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: none;
+ border: 1px solid var(--border);
+ border-radius: var(--radius-sm);
+ color: var(--text-muted);
+ cursor: pointer;
+ font-size: 14px;
+ line-height: 1;
+ transition: all 0.15s;
+ flex-shrink: 0;
+}
+
+.closeBtn:hover {
+ color: var(--text-primary);
+ border-color: var(--text-faint);
+}
+
+.tabs {
+ display: flex;
+ border-bottom: 1px solid var(--border-subtle);
+ flex-shrink: 0;
+}
+
+.tab {
+ padding: 9px 16px;
+ font-size: 12px;
+ font-weight: 500;
+ font-family: var(--font-body);
+ color: var(--text-muted);
+ background: none;
+ border: none;
+ border-bottom: 2px solid transparent;
+ cursor: pointer;
+ transition: all 0.15s;
+ white-space: nowrap;
+}
+
+.tab:hover {
+ color: var(--text-secondary);
+}
+
+.tab.activeTab {
+ color: var(--amber);
+ border-bottom-color: var(--amber);
+}
+
+.body {
+ flex: 1;
+ overflow-y: auto;
+ padding: 16px;
+ background: var(--bg-raised);
+}
+
+.actions {
+ display: flex;
+ gap: 8px;
+ padding: 12px 16px;
+ border-top: 1px solid var(--border-subtle);
+ flex-shrink: 0;
+ background: var(--bg-surface);
+}
diff --git a/src/design-system/composites/DetailPanel/DetailPanel.tsx b/src/design-system/composites/DetailPanel/DetailPanel.tsx
new file mode 100644
index 0000000..cd7f3ae
--- /dev/null
+++ b/src/design-system/composites/DetailPanel/DetailPanel.tsx
@@ -0,0 +1,67 @@
+import { useState, type ReactNode } from 'react'
+import styles from './DetailPanel.module.css'
+
+interface Tab {
+ label: string
+ value: string
+ content: ReactNode
+}
+
+interface DetailPanelProps {
+ open: boolean
+ onClose: () => void
+ title: string
+ tabs: Tab[]
+ actions?: ReactNode
+ className?: string
+}
+
+export function DetailPanel({ open, onClose, title, tabs, actions, className }: DetailPanelProps) {
+ const [activeTab, setActiveTab] = useState(tabs[0]?.value ?? '')
+
+ const activeContent = tabs.find((t) => t.value === activeTab)?.content
+
+ return (
+
+ )
+}
diff --git a/src/design-system/composites/Modal/Modal.module.css b/src/design-system/composites/Modal/Modal.module.css
new file mode 100644
index 0000000..c12c487
--- /dev/null
+++ b/src/design-system/composites/Modal/Modal.module.css
@@ -0,0 +1,71 @@
+.backdrop {
+ position: fixed;
+ inset: 0;
+ background: rgba(26, 22, 18, 0.55);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 1000;
+ padding: 24px;
+ animation: backdropIn 0.15s ease-out;
+}
+
+@keyframes backdropIn {
+ from { opacity: 0; }
+ to { opacity: 1; }
+}
+
+.dialog {
+ width: 100%;
+ background: var(--bg-surface);
+ border: 1px solid var(--border-subtle);
+ border-radius: var(--radius-lg);
+ box-shadow: var(--shadow-lg);
+ overflow: hidden;
+ animation: dialogIn 0.2s ease-out;
+}
+
+@keyframes dialogIn {
+ from { opacity: 0; transform: scale(0.97) translateY(-8px); }
+ to { opacity: 1; transform: scale(1) translateY(0); }
+}
+
+.header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 16px 20px;
+ border-bottom: 1px solid var(--border-subtle);
+}
+
+.title {
+ font-size: 16px;
+ font-weight: 600;
+ color: var(--text-primary);
+ margin: 0;
+}
+
+.closeBtn {
+ width: 28px;
+ height: 28px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: none;
+ border: 1px solid var(--border);
+ border-radius: var(--radius-sm);
+ color: var(--text-muted);
+ cursor: pointer;
+ font-size: 16px;
+ line-height: 1;
+ transition: all 0.15s;
+}
+
+.closeBtn:hover {
+ color: var(--text-primary);
+ border-color: var(--text-faint);
+}
+
+.body {
+ padding: 20px;
+}
diff --git a/src/design-system/composites/Modal/Modal.test.tsx b/src/design-system/composites/Modal/Modal.test.tsx
new file mode 100644
index 0000000..010c737
--- /dev/null
+++ b/src/design-system/composites/Modal/Modal.test.tsx
@@ -0,0 +1,38 @@
+import { describe, it, expect, vi } from 'vitest'
+import { render, screen } from '@testing-library/react'
+import userEvent from '@testing-library/user-event'
+import { Modal } from './Modal'
+
+describe('Modal', () => {
+ it('renders children when open', () => {
+ render(