diff --git a/src/_includes/partials/now/playing.liquid b/src/_includes/partials/now/playing.liquid
index 7c9ae4d2..473ce5f1 100644
--- a/src/_includes/partials/now/playing.liquid
+++ b/src/_includes/partials/now/playing.liquid
@@ -1,2 +1,4 @@
 <script type="module" src="/assets/scripts/components/now-playing.js"></script>
-<now-playing></now-playing>
\ No newline at end of file
+<div class="client-side" style="height:28px;margin-bottom:1rem">
+  <now-playing></now-playing>
+</div>
\ No newline at end of file
diff --git a/src/assets/scripts/components/now-playing.js b/src/assets/scripts/components/now-playing.js
index ea11f3d6..8aa51547 100644
--- a/src/assets/scripts/components/now-playing.js
+++ b/src/assets/scripts/components/now-playing.js
@@ -1,7 +1,7 @@
 const nowPlayingTemplate = document.createElement('template')
 
 nowPlayingTemplate.innerHTML = `
-  <p class="client-side">
+  <p>
     <span class="text--blurred fade" data-key="loading">🎧 Album by Artist</span>
     <span>
       <span class="fade" data-key="content" style="opacity:0"></span>
diff --git a/src/posts/2024/building-a-bespoke-now-playing-web-component.md b/src/posts/2024/building-a-bespoke-now-playing-web-component.md
new file mode 100644
index 00000000..2f006f60
--- /dev/null
+++ b/src/posts/2024/building-a-bespoke-now-playing-web-component.md
@@ -0,0 +1,86 @@
+---
+date: '2024-02-21'
+title: 'Building a bespoke now-playing web component'
+description: "I've long had a now playing element on the home page of my site that displays either what I've checked into on Trakt, the Lakers' record and who they're playing when a game is on or the last song I've listened to. After leveraging some new web components on my site, I decided to refactor the code powering this into a web component specific to my needs."
+tags: ['development', 'javascript', 'Eleventy']
+---
+I've long had a now playing element on the home page of my site that displays either what I've checked into on Trakt, the Lakers' record and who they're playing when a game is on or the last song I've listened to. After leveraging some new web components on my site, I decided to refactor the code powering this into a web component specific to my needs.<!-- excerpt -->
+
+I start by creating the template for the component and setting the `id` before then adding it to the document body:
+
+```javascript
+const nowPlayingTemplate = document.createElement('template')
+
+nowPlayingTemplate.innerHTML = `
+  <p>
+    <span class="text--blurred" data-key="loading">🎧 Album by Artist</span>
+    <span>
+      <span data-key="content" style="opacity:0"></span>
+    </span>
+  </p>
+`
+
+nowPlayingTemplate.id = "now-playing-template"
+
+if (!document.getElementById(nowPlayingTemplate.id)) document.body.appendChild(nowPlayingTemplate)
+```
+
+Next, I define the `NowPlaying` class and register the custom element:
+
+```javascript
+class NowPlaying extends HTMLElement {
+  static register(tagName) {
+    if ("customElements" in window) {
+      customElements.define(tagName || "now-playing", NowPlaying);
+    }
+  }
+...
+```
+
+Next, in the `connectedCallback()` method, we handle appending the template to the `NowPlaying` element and populate the data for the component:
+
+```javascript
+async connectedCallback() {
+  this.append(this.template);
+  const data = { ...(await this.data) };
+  const loading = this.querySelector('[data-key="loading"]')
+  const content = this.querySelector('[data-key="content"]')
+  const value = data['content']
+
+  loading.style.opacity = '0'
+  loading.style.display = 'none'
+  content.style.opacity = '1'
+  content.innerHTML = value
+}
+```
+
+The logic here is quite straightforward. It appends the template to the custom element, fetches the data from my `/api/now-playing` endpoint, caches query selectors for the component and then sets opacity, display styles and component content.
+
+The `fade` class that animates component loading is as follows:
+
+```css
+/* transitions */
+--transition-ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
+--transition-duration-default: 300ms;
+...
+.fade {
+  transition-property: opacity;
+  transition-timing-function: var(--transition-ease-in-out);
+  transition-duration: var(--transition-duration-default);
+}
+```
+
+The component is then included via a `playing.liquid` template:
+{% raw %}
+```liquid
+<script type="module" src="/assets/scripts/components/now-playing.js"></script>
+<!-- I'm just a div, here to prevent layout shifts -->
+<div class="client-side" style="height:28px;margin-bottom:1rem">
+  <now-playing></now-playing>
+</div>
+```
+{% endraw %}
+
+Now, instead of having a separate template for the component and script, I'm able to quite simply consolidate the two and provide the same experience. You can view the full source of the component [here](https://github.com/cdransf/coryd.dev/blob/main/src/assets/scripts/components/now-playing.js) and the source of the edge function powering the `/api/now-playing` endpoint [here](https://github.com/cdransf/coryd.dev/blob/main/netlify/edge-functions/now-playing.js).
+
+Building this first component was pretty straightforward and, frankly, fun — it encapsulates the rendering logic and data fetching in one place without any external dependencies.
\ No newline at end of file