diff --git a/agenda/types.py b/agenda/types.py
index c0f28b1..d1668c5 100644
--- a/agenda/types.py
+++ b/agenda/types.py
@@ -66,6 +66,25 @@ class Trip:
         max_date = max(max_conference_end, travel_end, accommodation_end)
         return max_date if max_date != datetime.date.min else None
 
+    def locations(self) -> list[tuple[str, Country]]:
+        """Locations for trip."""
+        seen: set[tuple[str, str]] = set()
+        items = []
+
+        for item in self.conferences + self.accommodation + self.events:
+            if "country" not in item or "location" not in item:
+                continue
+            key = (item["location"], item["country"])
+            if key in seen:
+                continue
+            seen.add(key)
+
+            country = agenda.get_country(item["country"])
+            assert country
+            items.append((item["location"], country))
+
+        return items
+
     @property
     def countries(self) -> list[Country]:
         """Countries visited as part of trip, in order."""
@@ -90,6 +109,13 @@ class Trip:
             [f"{c.flag} {c.name}" for c in self.countries]
         )
 
+    @property
+    def locations_str(self) -> str:
+        """List of countries visited on this trip."""
+        return format_list_with_ampersand(
+            [f"{location} {c.flag}" for location, c in self.locations()]
+        )
+
     @property
     def country_flags(self) -> str:
         """Countries flags for trip."""
diff --git a/templates/trip_list_text.html b/templates/trip_list_text.html
new file mode 100644
index 0000000..a5aa5b5
--- /dev/null
+++ b/templates/trip_list_text.html
@@ -0,0 +1,67 @@
+{% extends "base.html" %}
+
+{% from "macros.html" import trip_link, display_date_no_year, display_date, conference_row, accommodation_row, flight_row, train_row with context %}
+
+{% set row = { "flight": flight_row, "train": train_row } %}
+
+{% block style %}
+
+ <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
+     integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
+     crossorigin=""/>
+
+{% set conference_column_count = 7 %}
+{% set accommodation_column_count = 7 %}
+{% set travel_column_count = 8 %}
+<style>
+.conferences {
+  display: grid;
+  grid-template-columns: repeat({{ conference_column_count }}, auto); /* 7 columns for each piece of information */
+  gap: 10px;
+  justify-content: start;
+}
+
+.accommodation {
+  display: grid;
+  grid-template-columns: repeat({{ accommodation_column_count }}, auto);
+  gap: 10px;
+  justify-content: start;
+}
+
+.travel {
+  display: grid;
+  grid-template-columns: repeat({{ travel_column_count }}, auto);
+  gap: 10px;
+  justify-content: start;
+}
+
+.grid-item {
+  /* Additional styling for grid items can go here */
+}
+
+.map {
+  height: 80vh;
+}
+
+
+
+</style>
+{% endblock %}
+
+
+{% block content %}
+<div class="p-2">
+
+  <h1>Trips</h1>
+  <p>{{ future | count }} trips</p>
+  {% for trip in future %}
+    {% set end = trip.end %}
+    <div>
+      {{ display_date_no_year(trip.start) }} to {{ display_date_no_year(end) }}:
+      {{ trip.title }} &mdash; {{ trip.locations_str }}
+    </div>
+  {% endfor %}
+
+
+</div>
+{% endblock %}
diff --git a/web_view.py b/web_view.py
index 9d72286..389b7c0 100755
--- a/web_view.py
+++ b/web_view.py
@@ -220,6 +220,23 @@ def trip_list() -> str:
     )
 
 
+@app.route("/trip/text")
+def trip_list_text() -> str:
+    """Page showing a list of trips."""
+    trip_list = agenda.trip.build_trip_list()
+
+    today = date.today()
+    future = [item for item in trip_list if item.start > today]
+
+    return flask.render_template(
+        "trip_list_text.html",
+        future=future,
+        today=today,
+        get_country=agenda.get_country,
+        format_list_with_ampersand=format_list_with_ampersand,
+    )
+
+
 def human_readable_delta(future_date: date) -> str | None:
     """
     Calculate the human-readable time delta for a given future date.