Extend Panel Components Without Overwriting Them

Ever wanted to add a small feature to a built-in panel component but hesitated because you didn't want to lose future Kirby updates? I've been there. Today I want to show you a clean way to extend panel components without overwriting them entirely.

The Problem

Let's say you have a heading block and you've added two custom fields: a style field (letting editors choose visual styles independent of the semantic level) and an text alignment field. Both work perfectly fine in the block drawer.

But here's the thing: the preview in the blocks field doesn't reflect those choices. The editor has no idea if a heading is centered or styled differently until they open the block again. That's not great UX.

The obvious solution would be to overwrite the k-block-type-heading component. Copy the whole thing, modify it, done. But then you're on the hook for maintaining that code whenever Kirby updates its panel. Not ideal.

A Better Approach: Extend, Don't Overwrite

Instead of replacing the component, we can extend it. Vue (and Kirby's panel plugin system) makes this surprisingly straightforward.

I've set up a minimal plugin with just two files:

plugins/panel-extend/
├── index.js
└── index.css

Both files get autoloaded by the panel. No extra registration needed.

The JavaScript

In index.js, we define the plugin and extend the existing component:

window.panel.plugin("your-name/panel-extend", {
  components: {
    "k-block-type-heading": {
      extends: "k-block-type-heading",
      
      methods: {
        updateDataAttributes() {
          const { alignment, style } = this.content;
          if(!this.$el) return;
          this.$el.dataset.alignment = alignment
          this.$el.dataset.style = style
        }
      },
      
      watch: {
        content: {
          handler() {
            this.updateDataAttributes();
          },
          deep: true
        }
      },
      
      mounted() {
        this.updateDataAttributes();
      }
    }
  }
});

What's happening here:

  1. We use extends: "k-block-type-heading" to inherit all the existing component code.
  2. We add a method that reads style and alignment from the block content and sets them as data attributes on the element.
  3. We watch for content changes and update the attributes when the editor makes selections.
  4. On mount, we set the initial values.

That's it for the JS. We're not touching any of the original rendering logic, just adding data attributes that CSS can hook into.

The CSS

In index.css, we use those data attributes to style the preview:

/* Styles */
.k-block-type-heading-input[data-style="h1"] {
  font-size: var(--text-3xl);
}
.k-block-type-heading-input[data-style="h2"] {
  font-size: var(--text-2xl);
}
.k-block-type-heading-input[data-style="h3"] {
  font-size: var(--text-xl);
}
/* ... h4, h5, h6 */

/* Alignment */
.k-block-type-heading-input[data-alignment="left"] {
  text-align: left;
}
.k-block-type-heading-input[data-alignment="center"] {
  text-align: center;
}
.k-block-type-heading-input[data-alignment="right"] {
  text-align: right;
}

The Result

Now when an editor changes the style or alignment in the block settings, the preview updates instantly. They can see that a heading is centered or styled as an H1 without having to open the drawer.

One thing to keep in mind: these are panel-only styles. Your frontend snippet still needs to handle the actual output. But for the editing experience, this makes a real difference.

Why This Matters

Small touches like this help editors understand what they're building. A live preview that reflects their choices reduces friction and makes the panel feel more polished.

And the best part? When Kirby releases an update with improvements to the heading block component, you'll get those for free. Your extension just adds to the existing code, it doesn't replace it.

Give it a try. Extend what you need, keep what works.