diff --git a/web/src/lib/components/AlphabeticalGrid.svelte b/web/src/lib/components/AlphabeticalGrid.svelte
new file mode 100644
index 00000000..33f2c687
--- /dev/null
+++ b/web/src/lib/components/AlphabeticalGrid.svelte
@@ -0,0 +1,70 @@
+
+
+
+ {#each segments as seg, i (i)}
+ {#if seg.divider}
+
+ {seg.divider}
+
+
+ {/if}
+
+ {@render item(seg.item)}
+
+ {/each}
+
+
+
diff --git a/web/src/lib/components/AlphabeticalGrid.test.ts b/web/src/lib/components/AlphabeticalGrid.test.ts
new file mode 100644
index 00000000..c2822b8a
--- /dev/null
+++ b/web/src/lib/components/AlphabeticalGrid.test.ts
@@ -0,0 +1,51 @@
+import { describe, expect, test } from 'vitest';
+import { render, screen } from '@testing-library/svelte';
+import { createRawSnippet } from 'svelte';
+import AlphabeticalGrid from './AlphabeticalGrid.svelte';
+
+type Item = { id: string; key: string; label: string };
+
+const items: Item[] = [
+ { id: '1', key: 'A', label: 'Apple' },
+ { id: '2', key: 'A', label: 'Avocado' },
+ { id: '3', key: 'B', label: 'Banana' },
+ { id: '4', key: 'D', label: 'Date' } // skips C — divider must jump to D directly
+];
+
+// createRawSnippet receives getters: each param is () => ParamType
+const itemSnippet = createRawSnippet<[Item]>((getIt) => ({
+ render: () => `${getIt().label}`
+}));
+
+// The generic component's T parameter can't be inferred by testing-library's render,
+// so we cast props to avoid a spurious unknown-assignability error.
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+type AnyProps = any;
+
+describe('AlphabeticalGrid', () => {
+ test('inserts a divider for each new key letter', () => {
+ render(AlphabeticalGrid, {
+ props: {
+ items,
+ getKey: (it: Item) => it.key,
+ item: itemSnippet
+ } as AnyProps
+ });
+ // Three dividers (A, B, D); C is absent because no items use C.
+ expect(screen.getByText('A')).toBeInTheDocument();
+ expect(screen.getByText('B')).toBeInTheDocument();
+ expect(screen.getByText('D')).toBeInTheDocument();
+ expect(screen.queryByText('C')).not.toBeInTheDocument();
+ });
+
+ test('renders items in given order', () => {
+ const { container } = render(AlphabeticalGrid, {
+ props: {
+ items,
+ getKey: (it: Item) => it.key,
+ item: itemSnippet
+ } as AnyProps
+ });
+ expect(container.textContent).toMatch(/A.*Apple.*Avocado.*B.*Banana.*D.*Date/s);
+ });
+});