diff --git a/_config.ts b/_config.ts
index 9bbfb38..c5092ba 100644
--- a/_config.ts
+++ b/_config.ts
@@ -18,6 +18,9 @@ const site = lume({
   src: "src",
   // Set the hosting location
   location: new URL("https://open-innovations.github.io/lume-expo"),
+  components: {
+    cssFile: "_includes/css/components.css",
+  },
 });
 
 /**
@@ -49,7 +52,7 @@ site.filter("resultTable", resultTable);
 
 /**
  * Set up the metas plugin for SEO.
- * 
+ *
  * See https://lume.land/plugins/metas/#installation
  */
 site.use(metas());
diff --git a/src/_components/link/Grid.vto b/src/_components/link/Grid.vto
new file mode 100644
index 0000000..4807033
--- /dev/null
+++ b/src/_components/link/Grid.vto
@@ -0,0 +1,30 @@
+---
+css: |
+  ul.link-grid {
+    padding: unset;
+    list-style: none;
+    display: flex;
+    flex-wrap: wrap;
+    gap: 1em;
+  }
+  ul.link-grid > * {
+    flex-basis: 10em;
+    flex-grow: 0;
+    flex-shrink: 1;
+  }
+  ul.link-grid a.content {
+    display: block;
+    padding: 1em;
+  }
+  ul.link-grid a.content:hover {
+    color: var(--link-hover-color, white);
+    background: var(--link-hover-background, black);
+  }
+---
+<ul class="link-grid">
+  {{ for link of links }}
+    <li>
+      <a class="content" href="{{ link.url }}">{{ link.title }}</a>
+    </li>
+  {{ /for }}
+</ul>
\ No newline at end of file
diff --git a/src/_data.yml b/src/_data.yml
index 4709aab..0df5f7d 100644
--- a/src/_data.yml
+++ b/src/_data.yml
@@ -7,4 +7,8 @@ repo: https://github.com/open-innovations/lume-expo
 metas:
   site: OI Lume Expo
   title: "=title"
-  description: "=description"
\ No newline at end of file
+  description: "=description"
+
+techniques:
+  - Core
+  - Data
\ No newline at end of file
diff --git a/src/_includes/layout/catalogue.vto b/src/_includes/layout/catalogue.vto
new file mode 100644
index 0000000..1c8cb49
--- /dev/null
+++ b/src/_includes/layout/catalogue.vto
@@ -0,0 +1,4 @@
+---
+layout: ./page.vto
+---
+{{ comp.link.Grid({ links: catalogue }) }}
diff --git a/src/css/site.css b/src/css/site.css
index fa51b6d..a685d5b 100644
--- a/src/css/site.css
+++ b/src/css/site.css
@@ -1,2 +1,4 @@
 @import 'css/dessicate.css';
-@import 'css/code_theme.css';
\ No newline at end of file
+@import 'css/code_theme.css';
+
+@import 'css/components.css';
\ No newline at end of file
diff --git a/src/data/_data.yml b/src/data/_data.yml
deleted file mode 100644
index 779c391..0000000
--- a/src/data/_data.yml
+++ /dev/null
@@ -1 +0,0 @@
-technique: data
\ No newline at end of file
diff --git a/src/index.vto b/src/index.vto
index 265257a..53e5fcd 100644
--- a/src/index.vto
+++ b/src/index.vto
@@ -5,10 +5,7 @@ title: Home
   This site collects some common approaches to building Lume sites in use across Open Innovations.
 </p>
 
-<h2>Data techniques</h2>
-
-<ul>
-{{ for technique of search.pages('technique=data', 'title') }}
-  <li><a href="{{technique.url}}">{{ technique.title }}</a></li>
+{{ for technique of search.pages('technique') }}
+  <h2>{{ technique.title }}</h2>
+  {{ comp.link.Grid({ links: technique.catalogue }) }}
 {{ /for }}
-</ul>
\ No newline at end of file
diff --git a/src/techniques.page.ts b/src/techniques.page.ts
new file mode 100644
index 0000000..02824e7
--- /dev/null
+++ b/src/techniques.page.ts
@@ -0,0 +1,20 @@
+export const layout = "layout/catalogue.vto";
+
+export const tags = ["technique"];
+
+const capitalise = (s: string) => s.charAt(0).toUpperCase() + s.slice(1);
+
+export default function* ({ search }: Lume.Data) {
+  const techniques = search.values<string>("technique");
+
+  for (const technique of techniques) {
+    const pages = search.pages(`technique=${technique}`).map(
+      ({ title, url }) => ({ title, url }),
+    );
+    yield {
+      title: capitalise(technique),
+      url: `/${technique}/`,
+      catalogue: pages,
+    };
+  }
+}
diff --git a/src/techniques/_data.ts b/src/techniques/_data.ts
new file mode 100644
index 0000000..3321b26
--- /dev/null
+++ b/src/techniques/_data.ts
@@ -0,0 +1,3 @@
+// Replace the url prefix with the grouping of the technique.
+export const url = (page: Lume.Page) =>
+  page.data.url.replace(/techniques/, page.data.technique);
diff --git a/src/data/duckdb/_data/auto_inc.sql b/src/techniques/duckdb/_data/auto_inc.sql
similarity index 100%
rename from src/data/duckdb/_data/auto_inc.sql
rename to src/techniques/duckdb/_data/auto_inc.sql
diff --git a/src/data/duckdb/_data/filtered/auto.sql b/src/techniques/duckdb/_data/filtered/auto.sql
similarity index 100%
rename from src/data/duckdb/_data/filtered/auto.sql
rename to src/techniques/duckdb/_data/filtered/auto.sql
diff --git a/src/data/duckdb/_data/filtered/cast.sql b/src/techniques/duckdb/_data/filtered/cast.sql
similarity index 100%
rename from src/data/duckdb/_data/filtered/cast.sql
rename to src/techniques/duckdb/_data/filtered/cast.sql
diff --git a/src/data/duckdb/_data/filtered/parquet.sql b/src/techniques/duckdb/_data/filtered/parquet.sql
similarity index 100%
rename from src/data/duckdb/_data/filtered/parquet.sql
rename to src/techniques/duckdb/_data/filtered/parquet.sql
diff --git a/src/data/duckdb/_data/local_csv.sql b/src/techniques/duckdb/_data/local_csv.sql
similarity index 100%
rename from src/data/duckdb/_data/local_csv.sql
rename to src/techniques/duckdb/_data/local_csv.sql
diff --git a/src/data/duckdb/_data/local_parquet.sql b/src/techniques/duckdb/_data/local_parquet.sql
similarity index 100%
rename from src/data/duckdb/_data/local_parquet.sql
rename to src/techniques/duckdb/_data/local_parquet.sql
diff --git a/src/data/duckdb/_data/positional_params.sql b/src/techniques/duckdb/_data/positional_params.sql
similarity index 100%
rename from src/data/duckdb/_data/positional_params.sql
rename to src/techniques/duckdb/_data/positional_params.sql
diff --git a/src/data/duckdb/_data/remote/describe.sql b/src/techniques/duckdb/_data/remote/describe.sql
similarity index 100%
rename from src/data/duckdb/_data/remote/describe.sql
rename to src/techniques/duckdb/_data/remote/describe.sql
diff --git a/src/data/duckdb/_data/remote/reshape.ts b/src/techniques/duckdb/_data/remote/reshape.ts
similarity index 100%
rename from src/data/duckdb/_data/remote/reshape.ts
rename to src/techniques/duckdb/_data/remote/reshape.ts
diff --git a/src/data/duckdb/_data/simple.sql b/src/techniques/duckdb/_data/simple.sql
similarity index 100%
rename from src/data/duckdb/_data/simple.sql
rename to src/techniques/duckdb/_data/simple.sql
diff --git a/src/data/duckdb/index.vto b/src/techniques/duckdb/index.vto
similarity index 99%
rename from src/data/duckdb/index.vto
rename to src/techniques/duckdb/index.vto
index ad0d670..83a023d 100644
--- a/src/data/duckdb/index.vto
+++ b/src/techniques/duckdb/index.vto
@@ -1,8 +1,7 @@
 ---
 title: DuckDB
+technique: data
 ---
-<h1>{{ title }}</h1>
-
 <p>
   As more data is rendered in a site, the build can slow down significantly.
   It may be beneficial under these circumstances to use DuckDB to access data.