Skip to content

Bar Widget Development

Bar widgets are components that appear in the top or bottom bar of Noctalia Shell. They provide quick access to information and actions.

A bar widget is a QML file that must follow this structure:

import QtQuick
import QtQuick.Layouts
import Quickshell
import qs.Commons
import qs.Widgets
Rectangle {
id: root
// Plugin API (injected by PluginService)
property var pluginApi: null
// Required properties for bar widgets
property ShellScreen screen
property string widgetId: ""
property string section: ""
// Widget dimensions
implicitWidth: content.implicitWidth + Style.marginM * 2
implicitHeight: Style.barHeight
// Your widget content here
}

Injected by the PluginService. Provides access to plugin APIs and services.

property var pluginApi: null

The ShellScreen this widget is displayed on (for multi-monitor support).

property ShellScreen screen

Unique identifier for this widget instance.

property string widgetId: ""

The bar section this widget is in: "left", "center", or "right".

property string section: ""

Bar widgets should set their size appropriately:

// Horizontal bar (top/bottom)
implicitWidth: content.implicitWidth + Style.marginM * 2
implicitHeight: Style.barHeight
// Vertical bar (left/right) - adapt to bar position
readonly property string barPosition: Settings.data.bar.position
readonly property bool isVertical: barPosition === "left" || barPosition === "right"
implicitWidth: isVertical ? Style.capsuleHeight : content.implicitWidth + Style.marginM * 2
implicitHeight: isVertical ? content.implicitHeight + Style.marginM * 2 : Style.capsuleHeight

Use Noctalia’s built-in styling system for consistency:

import qs.Commons
Rectangle {
// Surface colors
color: Color.mSurface
color: Color.mSurfaceVariant
color: Style.capsuleColor // Recommended for bar widgets
// Text colors
NText {
color: Color.mOnSurface
color: Color.mOnSurfaceVariant
color: Color.mPrimary
}
}
import qs.Commons
ColumnLayout {
spacing: Style.marginS // Small spacing
spacing: Style.marginM // Medium spacing
spacing: Style.marginL // Large spacing
NIcon {
pointSize: Style.fontSizeS // Small icon
pointSize: Style.fontSizeM // Medium icon
pointSize: Style.fontSizeL // Large icon
}
}
Rectangle {
radius: Style.radiusS // Small radius
radius: Style.radiusM // Medium radius (recommended)
radius: Style.radiusL // Large radius
}
NText {
pointSize: Style.fontSizeXS
pointSize: Style.fontSizeS
pointSize: Style.fontSizeM
pointSize: Style.fontSizeL
font.weight: Font.Light
font.weight: Font.Normal
font.weight: Font.Medium
font.weight: Font.Bold
}

Access plugin settings via pluginApi:

// Get setting with fallback to default
readonly property string message:
pluginApi?.pluginSettings?.message ||
pluginApi?.manifest?.metadata?.defaultSettings?.message ||
"Default Message"
// Update and save setting
function updateMessage(newMessage) {
pluginApi.pluginSettings.message = newMessage
pluginApi.saveSettings()
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
// Open plugin panel
pluginApi.openPanel(root.screen)
// Or perform action
performAction()
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
onEntered: {
root.color = Qt.lighter(root.color, 1.1)
}
onExited: {
root.color = Style.capsuleColor
}
}
import qs.Services.UI
import qs.Services.System
MouseArea {
anchors.fill: parent
hoverEnabled: true
onEntered: {
TooltipService.show(root, "Widget tooltip text", BarService.getTooltipDirection())
}
onExited: {
TooltipService.hide()
}
}

You can also use a function to build dynamic tooltip content:

function buildTooltip() {
return "Status: " + (enabled ? "Active" : "Inactive")
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
onEntered: {
TooltipService.show(root, buildTooltip(), BarService.getTooltipDirection())
}
onExited: {
TooltipService.hide()
}
}

The BarService.getTooltipDirection() function automatically determines the correct tooltip direction based on the bar’s position (top, bottom, left, or right).

If your plugin provides a panel, open it from the widget:

MouseArea {
anchors.fill: parent
onClicked: {
if (pluginApi) {
pluginApi.openPanel(root.screen)
}
}
}

Noctalia provides many pre-built widgets:

import qs.Widgets
NIcon {
icon: "heart" // Tabler icon name
color: Color.mPrimary
applyUiScale: true // Automatically scale with UI
}
NText {
text: "Hello World"
color: Color.mOnSurface
pointSize: Style.fontSizeM
font.weight: Font.Medium
}
NIconButton {
icon: "settings"
onClicked: {
// Handle click
}
}

Plugins can use Noctalia services:

import qs.Services.UI
ToastService.showNotice("Success message")
ToastService.showError("Error message")
import qs.Commons
Component.onCompleted: {
Logger.i("MyPlugin", "Widget loaded")
Logger.d("MyPlugin", "Debug info:", someValue)
Logger.w("MyPlugin", "Warning message")
Logger.e("MyPlugin", "Error occurred")
}
import qs.Commons
// Access global Noctalia settings
readonly property string barPosition: Settings.data.bar.position
readonly property bool isDarkMode: Settings.data.ui.darkMode
import QtQuick
import QtQuick.Layouts
import Quickshell
import qs.Commons
import qs.Widgets
Rectangle {
id: root
property var pluginApi: null
property ShellScreen screen
property string widgetId: ""
property string section: ""
implicitWidth: row.implicitWidth + Style.marginM * 2
implicitHeight: Style.barHeight
color: Style.capsuleColor
radius: Style.radiusM
RowLayout {
id: row
anchors.centerIn: parent
spacing: Style.marginS
NIcon {
icon: "check"
color: Color.mPrimary
}
NText {
text: "Ready"
color: Color.mOnSurface
pointSize: Style.fontSizeS
}
}
}
import QtQuick
import QtQuick.Layouts
import Quickshell
import qs.Commons
import qs.Widgets
import qs.Services.UI
Rectangle {
id: root
property var pluginApi: null
property ShellScreen screen
property string widgetId: ""
property string section: ""
property int count: pluginApi?.pluginSettings?.count || 0
implicitWidth: row.implicitWidth + Style.marginM * 2
implicitHeight: Style.barHeight
color: Style.capsuleColor
radius: Style.radiusM
RowLayout {
id: row
anchors.centerIn: parent
spacing: Style.marginS
NIcon {
icon: "numbers"
color: Color.mPrimary
}
NText {
text: root.count.toString()
color: Color.mOnSurface
pointSize: Style.fontSizeM
font.weight: Font.Bold
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onEntered: {
root.color = Qt.lighter(Style.capsuleColor, 1.1)
}
onExited: {
root.color = Style.capsuleColor
}
onClicked: {
root.count++
pluginApi.pluginSettings.count = root.count
pluginApi.saveSettings()
ToastService.showNotice("Count: " + root.count)
}
}
}
import QtQuick
import QtQuick.Layouts
import Quickshell
import qs.Commons
import qs.Widgets
Rectangle {
id: root
property var pluginApi: null
property ShellScreen screen
property string widgetId: ""
property string section: ""
readonly property string message:
pluginApi?.pluginSettings?.message ||
pluginApi?.manifest?.metadata?.defaultSettings?.message || ""
implicitWidth: row.implicitWidth + Style.marginM * 2
implicitHeight: Style.barHeight
color: Style.capsuleColor
radius: Style.radiusM
RowLayout {
id: row
anchors.centerIn: parent
spacing: Style.marginS
NIcon {
icon: "noctalia"
}
NText {
text: root.message
color: Color.mOnSurface
pointSize: Style.fontSizeS
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onEntered: {
root.color = Qt.lighter(Style.capsuleColor, 1.1)
}
onExited: {
root.color = Style.capsuleColor
}
onClicked: {
if (pluginApi) {
pluginApi.openPanel(root.screen)
}
}
}
}

Support vertical bars (left/right positions):

import QtQuick
import QtQuick.Layouts
import Quickshell
import qs.Commons
import qs.Widgets
Rectangle {
id: root
property var pluginApi: null
property ShellScreen screen
property string widgetId: ""
property string section: ""
readonly property string barPosition: Settings.data.bar.position
readonly property bool isVertical: barPosition === "left" || barPosition === "right"
implicitWidth: isVertical ? Style.capsuleHeight : layout.implicitWidth + Style.marginM * 2
implicitHeight: isVertical ? layout.implicitHeight + Style.marginM * 2 : Style.capsuleHeight
color: Style.capsuleColor
radius: Style.radiusM
Item {
id: layout
anchors.centerIn: parent
implicitWidth: rowLayout.visible ? rowLayout.implicitWidth : colLayout.implicitWidth
implicitHeight: rowLayout.visible ? rowLayout.implicitHeight : colLayout.implicitHeight
RowLayout {
id: rowLayout
visible: !root.isVertical
spacing: Style.marginS
NIcon {
icon: "heart"
color: Color.mPrimary
}
NText {
text: "Widget"
color: Color.mOnSurface
}
}
ColumnLayout {
id: colLayout
visible: root.isVertical
spacing: Style.marginS
NIcon {
icon: "heart"
color: Color.mPrimary
}
NText {
text: "Widget"
color: Color.mOnSurface
}
}
}
}
  1. Keep it small: Bar widgets should be compact and unobtrusive
  2. Use consistent styling: Stick to Noctalia’s design system
  3. Handle missing data: Always provide fallbacks for settings and data
  4. Respect dark mode: Use Color.m* colors that adapt to theme
  5. Add hover effects: Provide visual feedback for interactive elements
  6. Log important events: Use Logger for debugging
  7. Test on vertical bars: Ensure your widget works in all bar positions
  8. Optimize performance: Avoid expensive operations in the widget

Coming Soon

  • Settings UI - Add configuration UI
  • Styling Guide - Design system details