I built a thing for Buttondown that lets you embed an iFrame to handle subscriptions really nicely:

This thing necessitated a change in the UI: The current share page was pretty gross (below), and this was going to make it grosser:

The old Share page. Not super elegant.

I am admittedly not a great designer, and whenever I need to change UI I go spelunking in Bootstrap to see if I can find a paradigm that I like.

And I found something great! Bootstrap cards with tabbed navigation ended up looking really great:

The Bootstrap component. So pretty!

I don’t actually include Bootstrap in Buttondown, though. A lot of its design cues are taken from it (and some things, like modals, are almost identically styled), but I didn’t want to deal with the additional CSS + JS overhead, especially because the jQuery-esque way of handling interactivity seems anti-Vue.

This seemed like a job for a Vue components: I really wanted to be able to just have a tabbed-card component that I could then add various dynamic tabs with transclusion.

Turns out, you can! The code isn’t even that complicated: the trick is using dynamic slot names to handle the work for you. You still have to pass in a list of tab names as a prop, but I think that’s totally reasonable.

Here’s what the TabbedCard component looks like:

  <div class="card">
    <div class="card-header">
      <ul class="card-nav nav-tabs card-header-tabs">
        <li class="nav-item" v-for="tab in tabs">
            :class="activeTab === tab ? 'nav-link active' : 'nav-link'"
            @click="(e) => activeTab = tab"
    <div class="card-block">
      <slot :name="tab" v-for="tab in tabs" v-if="tab === activeTab" />

  export default {
    props: {
      tabs: Array,

    data() {
      return {
        activeTab: this.tabs[0],


And here’s how I invoke it, in the Share screen:

<tabbed-card :tabs="['as a link', 'as a form', 'as an iFrame']">
  <div slot="as a link">
    <!-- Stuff. -->

  <div slot="as a form">
    <!-- Stuff. -->

  <div slot="as an iFrame">
    <!-- Stuff.-->

This ends up being simple, and just leverages props and Vue’s transclusion functionality: we provide a tabs array, which creates both the actual tabs and a bunch of slots for content to go into. This is nice separation of concerns: the screen declaring the tabbed card doesn’t know about how the tabbed card is formed, it just supplies a list of tabs and content for each tab.

And here’s the finished product:


This is nothing groundbreaking, to be sure, but it’s really elegant and re-usable. It makes me happy when Vue makes it easy to create clean, maintainable components — it makes me feel like I’m doing the right thing, which can be an elusive feeling in software.

Liked this post? Follow me!