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:
- We use
extends: "k-block-type-heading"to inherit all the existing component code. - We add a method that reads
styleandalignmentfrom the block content and sets them as data attributes on the element. - We watch for content changes and update the attributes when the editor makes selections.
- 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.