diff --git a/package-lock.json b/package-lock.json
index dbf5b9eb..3487ecad 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "coryd.dev",
-  "version": "21.2.4",
+  "version": "21.3.0",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
       "name": "coryd.dev",
-      "version": "21.2.4",
+      "version": "21.3.0",
       "license": "MIT",
       "dependencies": {
         "@cdransf/api-text": "^1.4.0",
diff --git a/package.json b/package.json
index b66c3409..c13374b3 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "coryd.dev",
-  "version": "21.2.4",
+  "version": "21.3.0",
   "description": "The source for my personal site. Built using 11ty (and other tools).",
   "type": "module",
   "scripts": {
diff --git a/src/assets/styles/components/modal.css b/src/assets/styles/components/modal.css
new file mode 100644
index 00000000..6df54957
--- /dev/null
+++ b/src/assets/styles/components/modal.css
@@ -0,0 +1,71 @@
+.modal-wrapper {
+  width: 100%;
+  height: 100%;
+  inset: 0;
+  background: rgba(0, 0, 0, 0.7);
+  position: fixed;
+  z-index: 1;
+
+  & .modal-body {
+    position: fixed;
+    background: var(--background-color);
+    border: 1px solid var(--gray-light);
+    max-height: 75%;
+    max-width: 75%;
+    inset: 12.5%;
+    overflow: scroll;
+    padding: var(--sizing-lg) var(--sizing-2xl);
+
+    & .modal-close {
+      position: absolute;
+      top: var(--sizing-lg);
+      right: var(--sizing-lg);
+      cursor: pointer;
+
+      & svg {
+        stroke: var(--accent-color);
+        transform: rotate(0deg);
+        transition: transform var(--transition-duration-default) var(--transition-ease-in-out);
+
+        &:hover,
+        &:focus,
+        &:active {
+          stroke: var(--accent-color-hover);
+          transform: rotate(8deg);
+        }
+      }
+    }
+  }
+}
+
+.modal-input {
+  display: none;
+
+  &:checked ~ .modal-wrapper {
+    display: block;
+  }
+
+  &:not(:checked) ~ .modal-wrapper {
+    display: none;
+  }
+}
+
+.modal-toggle {
+  cursor: pointer;
+  display: inline-flex;
+  vertical-align: middle;
+
+  & svg {
+    cursor: pointer;
+    stroke: var(--accent-color);
+    transform: rotate(0deg);
+    transition: transform var(--transition-duration-default) var(--transition-ease-in-out);
+
+    &:hover,
+    &:focus,
+    &:active {
+      stroke: var(--accent-color-hover);
+      transform: rotate(8deg);
+    }
+  }
+}
\ No newline at end of file
diff --git a/src/assets/styles/index.css b/src/assets/styles/index.css
index 9ef27649..0dbf6b08 100644
--- a/src/assets/styles/index.css
+++ b/src/assets/styles/index.css
@@ -32,6 +32,7 @@
 @import url('./components/forms.css') layer(components);
 @import url('./components/media-grid.css') layer(components);
 @import url('./components/menu.css') layer(components);
+@import url('./components/modal.css') layer(components);
 @import url('./components/music-chart.css') layer(components);
 @import url('./components/paginator.css') layer(components);
 @import url('./components/progress-bar.css') layer(components);
diff --git a/src/assets/styles/pages/music.css b/src/assets/styles/pages/music.css
index 55d6a16d..c16aa478 100644
--- a/src/assets/styles/pages/music.css
+++ b/src/assets/styles/pages/music.css
@@ -85,6 +85,10 @@
 .concert-list {
   margin-top: 0;
   padding-left: 0;
+
+  & li .modal-toggle {
+    height: calc(var(--sizing-lg) * 1.6);
+  }
 }
 
 @media screen and (min-width: 768px) {
diff --git a/src/data/concerts.js b/src/data/concerts.js
index 96976521..8df3d573 100644
--- a/src/data/concerts.js
+++ b/src/data/concerts.js
@@ -46,6 +46,7 @@ const fetchAllConcerts = async () => {
 
 const processConcerts = (concerts) => {
   return concerts.map(concert => ({
+    id: concert['id'],
     date: concert['date'],
     artistNameString: concert['artist_name_string'],
     venue: {
diff --git a/src/includes/partials/blocks/modal.liquid b/src/includes/partials/blocks/modal.liquid
new file mode 100644
index 00000000..77642d50
--- /dev/null
+++ b/src/includes/partials/blocks/modal.liquid
@@ -0,0 +1,18 @@
+{%- capture labelContent -%}
+  {% if icon %}
+    {% tablericon icon label %}
+  {% elsif label %}
+    {{ label }}
+  {% endif %}
+{%- endcapture -%}
+{% assign modalId = id | default: "modal-controls" %}
+<input class="modal-input" id="{{ id }}" type="checkbox" tabindex="0" />
+<label class="modal-toggle" for="{{ id }}">{{ labelContent }}</label>
+<div class="modal-wrapper">
+  <div class="modal-body">
+    <label class="modal-close" for="{{ id }}">
+      {% tablericon "x" "Close modal" %}
+    </label>
+    {{ content }}
+  </div>
+</div>
\ No newline at end of file
diff --git a/src/pages/dynamic/concerts.html b/src/pages/dynamic/concerts.html
index becf3f49..b5ad204c 100644
--- a/src/pages/dynamic/concerts.html
+++ b/src/pages/dynamic/concerts.html
@@ -33,6 +33,10 @@ permalink: "/music/concerts/{% if pagination.pageNumber > 0 %}{{ pagination.page
     <li>
       {{ artistName }} on <strong class="highlight-text">{{ concert.date | date: "%B %e, %Y" }}</strong>
       {% if venue %} at {{ venue }}{% endif %}
+      {% if concert.notes %}
+        {% assign notes = concert.notes | prepend: "### Notes\n" | markdown %}
+        {% render "partials/blocks/modal.liquid", label:"Concert info", icon:"info-circle", content:notes, id:concert.id %}
+      {% endif %}
     </li>
   {% endfor %}
 </ul>