Skip to main content

Button Graphics Elements

info

This is a new feature in API 2.1 (Companion 5.0+), part of the graphics overhaul.

Companion 5.0 introduces an element-based drawing system for buttons. Instead of describing a button with a single flat style, you can build it up from a stack of graphics elements — text, images, boxes, lines, circles, gauges, and groups — each positioned and styled independently.

These elements are the building blocks for two things:

  • Layered presets — a new preset type that draws its button from a list of elements.
  • Composite elements — reusable drawing components your module defines, which can be used in layered presets and added by users to their own buttons.

This page is the reference for the element types themselves. The two pages above describe how to use them in context.

tip

You don't have to use any of this. The simple preset style is still fully supported and is the right choice whenever you don't need detailed custom drawing — it is simpler to write and is required for compatibility with Bitfocus Buttons. Reach for graphics elements only when a button genuinely needs richer graphics than a single style can express.

The coordinate model

A few conventions apply across all elements:

  • Positions and sizes (x, y, width, height, and the line endpoints) are expressed as percentages from 0 to 100 of the button, not pixels. This keeps your graphics resolution-independent, so they look right regardless of the button's pixel size.
  • Colors are packed RGB numbers, exactly as elsewhere in Companion — use combineRgb(255, 255, 255) or a hex literal like 0xffffff.
  • Rotation and angles are in degrees (0–359).
  • Opacity is a percentage from 0 to 100.

Values and expressions

Almost every property on an element is an expression-or-value. There are three ways to write one:

// A bare value — shorthand for a fixed value
50

// A fixed value (the explicit, wrapped form)
{ isExpression: false, value: 50 }

// An expression, evaluated live on the button
{ isExpression: true, value: '$(my-module:level) * 2' }

A bare value is accepted as a shortcut for { isExpression: false, value: ... }, exactly as with the options on actions and feedbacks. Reach for the wrapped form only when you need an expression, or when you want to be explicit.

This is what lets a layered button react to module variables and local variables — any property can be driven by an expression, not just the text.

{
type: 'text',
text: { isExpression: true, value: `$(my-module:channel_1_name)` },
color: { isExpression: false, value: 0xffffff },
}

Properties common to all elements

Every element shares this set of optional base properties:

PropertyTypeNotes
idstringA stable id for the element. Required if a feedback needs to target it for a style override.
namestringA friendly name shown in the Companion UI element list.
enabledbooleanWhen false, the element is not drawn. Useful when driven by an expression.
opacitynumber0–100. Defaults to fully opaque.

Most elements (everything except line) also share a set of bounds, defining where the element is drawn:

PropertyTypeNotes
xnumber0–100, left edge
ynumber0–100, top edge
widthnumber0–100
heightnumber0–100

Each of these values is an expression-or-value: pass a bare value for a fixed setting, or the wrapped form to drive it from an expression.

Element types

Each element is distinguished by its type field.

Text

{
type: 'text',
id: 'label',
x: { isExpression: false, value: 0 },
y: { isExpression: false, value: 0 },
width: { isExpression: false, value: 100 },
height: { isExpression: false, value: 100 },
text: { isExpression: true, value: `$(my-module:channel_name)` },
fontsize: { isExpression: false, value: 20 }, // 3-200, percentage of element height
fontsizeAllowShrink: { isExpression: false, value: true },
font: { isExpression: false, value: 'companion-sans' }, // 'companion-sans' | 'companion-mono'
color: { isExpression: false, value: 0xffffff },
halign: { isExpression: false, value: 'center' }, // 'left' | 'center' | 'right'
valign: { isExpression: false, value: 'center' }, // 'top' | 'center' | 'bottom'
outlineColor: { isExpression: false, value: 0x000000 },
}
PropertyTypeNotes
textstringThe text to draw (required).
fontsizenumber3–200, as a percentage of the element height.
fontsizeAllowShrinkbooleanLet the text shrink below fontsize when too long to fit.
fontfont family'companion-sans' | 'companion-mono'
colornumberText color.
halignalignment'left' | 'center' | 'right'
valignalignment'top' | 'center' | 'bottom'
outlineColornumberOptional outline color.
rotationnumberDegrees 0–359.

Image

{
type: 'image',
base64Image: { isExpression: true, value: `$(my-module:thumbnail_base64)` },
fillMode: { isExpression: false, value: 'fit' }, // 'crop' | 'fill' | 'fit'
halign: { isExpression: false, value: 'center' },
valign: { isExpression: false, value: 'center' },
}
PropertyTypeNotes
base64Imagestring | nullA data URI containing a base64-encoded PNG/JPG, e.g. data:image/png;base64,… (required).
fillModefill mode'crop' | 'fill' | 'fit'
halignalignment'left' | 'center' | 'right'
valignalignment'top' | 'center' | 'bottom'
rotationnumberDegrees 0–359.

Driving an image from a variable

The base64Image property can be useful as an expression that resolves to an image your module produces at runtime. The pattern has three parts:

  1. Encode the image as a data URI. Take your PNG/JPG bytes, base64-encode them, and prefix the result so the value is a full data URI like data:image/png;base64,iVBORw0KGgo…. A bare base64 string is not accepted — the data: prefix is required.
  2. Store the string somewhere the user can reference. Put it in a variable with setVariableValues(), or return it as the result of a value feedback (which the user binds to a local variable).
  3. Reference it from the Image element through the base64Image expression.
// 1 + 2: the module pushes a data URI into a variable
const dataUri = `data:image/png;base64,${pngBuffer.toString('base64')}`
this.setVariableValues({ thumbnail_base64: dataUri })
// 3: the Image element renders whatever that variable currently holds
{
type: 'image',
base64Image: { isExpression: true, value: `$(my-module:thumbnail_base64)` },
fillMode: { isExpression: false, value: 'fit' },
}

This works with any variable — a plain module variable, a custom variable, or a local variable populated by a value feedback. There is nothing image-specific about the variable itself; it is just a string that happens to be a data URI. If the variable is empty, null, or does not contain a valid image, the element simply draws nothing.

Box

A filled and/or stroked rectangle.

{
type: 'box',
x: { isExpression: false, value: 10 },
y: { isExpression: false, value: 10 },
width: { isExpression: false, value: 80 },
height: { isExpression: false, value: 80 },
color: { isExpression: false, value: 0x222222 }, // fill color
borderWidth: { isExpression: false, value: 2 },
borderColor: { isExpression: false, value: 0xff0000 },
}
PropertyTypeNotes
colornumberFill color.
rotationnumberDegrees 0–359.

Plus the shared border properties.

Line

A line segment between two points. The line is drawn using the border propertiesborderWidth is its thickness and borderColor its color.

{
type: 'line',
fromX: { isExpression: false, value: 0 },
fromY: { isExpression: false, value: 0 },
toX: { isExpression: false, value: 100 },
toY: { isExpression: false, value: 100 },
borderWidth: { isExpression: false, value: 3 },
borderColor: { isExpression: false, value: 0xffffff },
}
PropertyTypeNotes
fromXnumber0–100, start point
fromYnumber0–100, start point
toXnumber0–100, end point
toYnumber0–100, end point

Note that a line uses its own endpoints rather than the shared x/y/width/height bounds.

Circle

A filled circle, or an arc/pie slice.

{
type: 'circle',
x: { isExpression: false, value: 25 },
y: { isExpression: false, value: 25 },
width: { isExpression: false, value: 50 },
height: { isExpression: false, value: 50 },
color: { isExpression: false, value: 0x00cc00 },
startAngle: { isExpression: false, value: 0 },
endAngle: { isExpression: false, value: 270 },
drawSlice: { isExpression: false, value: true },
}
PropertyTypeNotes
colornumberFill color.
startAnglenumberDegrees 0–359, for drawing an arc.
endAnglenumberDegrees 0–359, for drawing an arc.
drawSlicebooleanWhen drawing an arc, close it as a pie slice.
borderOnlyArcbooleanOnly stroke the arc portion of the border.

Plus the shared border properties.

Gauge

A value-driven meter — a horizontal or vertical bar, or a circular ring — for showing a numeric value (a level, volume, percentage, temperature, and so on) directly on a button, without producing a custom image.

{
type: 'gauge',
x: 10,
y: 10,
width: 80,
height: 80,
orientation: 'ring', // 'horizontal' | 'vertical' | 'ring'
min: 0,
max: 100,
value: { isExpression: true, value: `$(my-module:level)` },
ringWidth: 20,
roundedEnds: true,
fillEnabled: true,
multiColour: true,
stops: [
{ value: 0, color: 0x00ff00, gradient: true },
{ value: 80, color: 0xffff00, gradient: true },
{ value: 95, color: 0xff0000, gradient: false },
],
trackStyle: 'dimmed',
trackAmount: 20,
}

Like every other element, each property below is an expression-or-value — pass a bare value, or the wrapped form to drive it from an expression. (The one exception is stops, which is a plain array; the fields inside each stop are expression-or-values.) Colors are packed RGB numbers.

Value:

PropertyTypeNotes
valuenumberThe current value, within the minmax range.
minnumberThe value mapped to the start of the gauge.
maxnumberThe value mapped to the end of the gauge.
originnumberThe value the fill grows from (defaults to the start).
symmetricbooleanGrow the fill in both directions from origin.

Appearance:

PropertyTypeNotes
orientationorientation'horizontal' | 'vertical' | 'ring'
reversebooleanFill from the opposite end.
rotationnumberDegrees 0–359.

Circular (when orientation is 'ring'):

PropertyTypeNotes
startAnglenumber0–360.
endAnglenumber0–360.
ringWidthnumberRing thickness as a percentage, 1–50.
roundedEndsbooleanRound the ends of the ring.

Fill:

PropertyTypeNotes
fillEnabledbooleanWhether the fill is drawn.
multiColourbooleanShow every stop as a gradient, rather than the single colour of the active stop.
stopsstop[]Colour stops — see below.

Each entry in stops is { value, color, gradient }:

PropertyTypeNotes
valuenumberThe value at which this stop applies, in minmax.
colornumberThe colour at this stop.
gradientbooleanBlend towards the next stop rather than stepping.

Marker:

PropertyTypeNotes
markerEnabledbooleanDraw a marker at the current value.
markerColornumberMarker colour.
markerWidthnumberMarker width as a percentage of the fill width, 1–100.

Track (the unfilled portion):

PropertyTypeNotes
trackStyletrack style'transparent' | 'dimmed'
trackAmountnumber0–100. 0 is black, 100 matches the active colour.
trackWidthnumber0–100, centred.
tip

A gauge is the recommended way to show a value as a bar or ring. Prefer it over producing a bitmap in an advanced feedback, which is harder to write and to maintain.

Group

A group nests other elements together so you can position, rotate, enable/disable, or fade them as one unit. Groups can be nested inside groups.

{
type: 'group',
x: { isExpression: false, value: 0 },
y: { isExpression: false, value: 50 },
width: { isExpression: false, value: 100 },
height: { isExpression: false, value: 50 },
rotation: { isExpression: false, value: 0 },
squareCoords: { isExpression: false, value: false },
children: [
{ type: 'box', /* ... */ },
{ type: 'text', /* ... */ },
],
}
PropertyTypeNotes
childrenelement[]The elements inside the group.
rotationnumberDegrees 0–359.
squareCoordsbooleanConstrain children to a centred square (the shorter side) coordinate space.

Composite

References one of the reusable composite elements your module defines. The elementId selects the definition, and options provides its input values.

{
type: 'composite',
elementId: 'signal-indicator',
options: {
level: { isExpression: true, value: `$(my-module:level)` },
},
}

See Composite Elements for how to define these.

Border properties

The box, line, and circle elements share a common set of border properties:

PropertyTypeNotes
borderWidthnumberBorder thickness. Set to 0 to disable.
borderColornumberBorder color.
borderPositionposition'inside' | 'center' | 'outside'

The canvas

A layered preset can optionally describe the base canvas the elements are drawn onto, via the canvas property. It controls the button's decoration (which replaces the old show_topbar toggle) and whether status icons are shown:

canvas: {
decoration: { isExpression: false, value: 'topbar' },
showStatusIcons: { isExpression: false, value: 'default' },
}

decoration is a ButtonGraphicsDecorationType and can be one of:

ValueMeaning
'default'Follow the user's global default.
'topbar'Show the top bar.
'border'Show a border.
'none'No decoration.

showStatusIcons is a ButtonGraphicsShowStatusIcons controlling the status icons drawn in the top-right corner of the button:

ValueMeaning
'default'Follow the user's global default.
'all'Show all status icons.
'none'Hide status icons.

Further Reading