diff --git a/src/pages/admin/AdminLayout.module.css b/src/pages/admin/AdminLayout.module.css
new file mode 100644
index 0000000..8ed3a4e
--- /dev/null
+++ b/src/pages/admin/AdminLayout.module.css
@@ -0,0 +1,53 @@
+.layout {
+ display: flex;
+ min-height: 100vh;
+}
+
+.sidebar {
+ width: 220px;
+ flex-shrink: 0;
+ background: var(--color-charcoal);
+ color: var(--color-cream);
+ padding: var(--space-lg) var(--space-md);
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-lg);
+}
+
+.brand {
+ font-family: var(--font-script);
+ font-size: 1.5rem;
+ color: var(--color-gold);
+}
+
+.sidebar nav {
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-sm);
+}
+
+.navLink {
+ color: var(--color-cream);
+ text-decoration: none;
+ padding: var(--space-sm);
+}
+
+.navLinkActive {
+ color: var(--color-gold);
+ font-weight: bold;
+}
+
+.signOut {
+ margin-top: auto;
+ background: none;
+ border: 1px solid var(--color-gold);
+ color: var(--color-cream);
+ padding: var(--space-sm);
+ cursor: pointer;
+}
+
+.content {
+ flex: 1;
+ padding: var(--space-xl) var(--space-lg);
+ background: var(--color-cream-alt);
+}
diff --git a/src/pages/admin/AdminLayout.test.tsx b/src/pages/admin/AdminLayout.test.tsx
new file mode 100644
index 0000000..f8f0d6b
--- /dev/null
+++ b/src/pages/admin/AdminLayout.test.tsx
@@ -0,0 +1,54 @@
+import { render, screen } from '@testing-library/react'
+import userEvent from '@testing-library/user-event'
+import { MemoryRouter, Routes, Route } from 'react-router-dom'
+import { describe, it, expect, vi, beforeEach } from 'vitest'
+
+const signOutMock = vi.fn()
+const navigateMock = vi.fn()
+
+vi.mock('../../lib/supabase', () => ({
+ supabase: { auth: { signOut: (...args: unknown[]) => signOutMock(...args) } },
+}))
+
+vi.mock('react-router-dom', async () => {
+ const actual = await vi.importActual('react-router-dom')
+ return { ...actual, useNavigate: () => navigateMock }
+})
+
+import AdminLayout from './AdminLayout'
+
+function renderLayout() {
+ return render(
+