diff --git a/src/component.rs b/src/component.rs
index 60215af60cacbc20adddf13af411b8f1b12cee89..1140c84a90cdd26a06cb6e7d7dcccacd55c4aadf 100644
--- a/src/component.rs
+++ b/src/component.rs
@@ -1,5 +1,7 @@
+// See component directory. This file exists to make the individual components accessible to the
+// rest of the code.
+
 pub mod arrow;
-/// See component directory. This file exists to make the individual components accessible to the
-/// rest of the code.
 pub mod code;
 pub mod grid;
+pub mod triangle;
diff --git a/src/component/arrow.rs b/src/component/arrow.rs
index ab9df927e728aecd4105b18ca3df4bd382945f00..957d3ffcffa04bdb375e06088f68f82574e0f6a7 100644
--- a/src/component/arrow.rs
+++ b/src/component/arrow.rs
@@ -4,6 +4,7 @@ use eframe::emath::Rot2;
 use std::cmp::Ordering;
 
 /// Pointy line used to emphasize something.
+#[must_use = "must call show"]
 pub struct Arrow {
     pub origin: Pos2,
     pub tip: Pos2,
diff --git a/src/component/triangle.rs b/src/component/triangle.rs
new file mode 100644
index 0000000000000000000000000000000000000000..05ebcc993f4091e495a7143bf89f43909c83650a
--- /dev/null
+++ b/src/component/triangle.rs
@@ -0,0 +1,32 @@
+use crate::egui::{Pos2, Stroke, Ui};
+use eframe::egui::{Color32, Shape};
+
+/// Three-point polygon.
+#[must_use = "must call show"]
+pub struct Triangle {
+    pub points: [Pos2; 3],
+    pub stroke: Color32,
+    pub stroke_width: f32,
+    pub fill: Color32,
+}
+
+impl Default for Triangle {
+    fn default() -> Self {
+        Self {
+            points: [Pos2::ZERO; 3],
+            stroke: Color32::BLACK,
+            stroke_width: 1.0,
+            fill: Color32::TRANSPARENT,
+        }
+    }
+}
+
+impl Triangle {
+    pub fn show(&self, ui: &mut Ui) {
+        ui.painter().add(Shape::convex_polygon(
+            self.points.into(),
+            self.fill,
+            Stroke::new(self.stroke_width, self.stroke),
+        ));
+    }
+}
diff --git a/src/slide.rs b/src/slide.rs
index 43cfc7528d79e6d78cf379758145c9269827f4e3..2151fb979419ee189416c00690a13aba30d78143 100644
--- a/src/slide.rs
+++ b/src/slide.rs
@@ -1,7 +1,7 @@
 use eframe::egui::{Context, Ui};
 
-/// See slide directory. This file exists to make the individual slides accessible to the
-/// rest of the code.
+// See slide directory. This file exists to make the individual slides accessible to the
+// rest of the code.
 pub mod s1_title;
 pub mod s2_introduction;
 pub mod s3_complexity;
diff --git a/src/slide/s7_mosaic.rs b/src/slide/s7_mosaic.rs
index df7ff6871f6adc8cb8f2ade590e1189037f8c796..588ed33d2ef9724266bd26c9fbd7c604085ef46d 100644
--- a/src/slide/s7_mosaic.rs
+++ b/src/slide/s7_mosaic.rs
@@ -1,7 +1,8 @@
-use crate::egui::Ui;
+use crate::component::triangle::Triangle;
+use crate::egui::{Color32, Ui};
 use crate::slide::Slide;
 use eframe::egui::style::Margin;
-use eframe::egui::Frame;
+use eframe::egui::{Frame, Pos2};
 
 #[derive(Default)]
 pub struct Mosaic {}
@@ -13,6 +14,18 @@ impl Slide for Mosaic {
             ui.heading("Going Further");
             ui.add_space(8.0);
             ui.label("TODO: Triangle mosaic");
+
+            Triangle {
+                points: [
+                    Pos2::new(200.0, 150.0),
+                    Pos2::new(500.0, 220.0),
+                    Pos2::new(300.0, 400.0),
+                ],
+                stroke_width: 0.0,
+                fill: Color32::RED,
+                ..Triangle::default()
+            }
+            .show(ui)
         });
     }
 }