Skip to content

Gauge

JS

A gauge shows how far a task has gone as a square frame with a number in the middle and a caption in the open bottom. Use a spin when you don’t know the percent yet and the mark sits next to a label. Use progress for a linear bar.

Give the host an id and hand it the reading:

<k-gauge id="upload" class="k-gauge"></k-gauge>
document.getElementById('upload').options = {
value: 64,
max: 100,
label: 'Upload',
format: '%',
};

The element writes a hidden <progress>, the frame, the reading, and the caption, and every repaint reuses the nodes it already made. Setting options also writes the modifier classes for variant, size, and indeterminate, so the tag reflects what you asked for. The fill eases when the value changes, and reduced motion drops the motion.

The reading follows value through format: leave it out for a plain number, pass % for a percent, or pass any other string ($, €, …) as a currency prefix. Change value and the dial text updates with it. Reach for text only when you need a one-off string format cannot write, like 12.4k. No value is an empty gauge, not a loading state. Pass indeterminate when you want the motion.

ClassTypeDescription
k-gaugecomponentThe one class you write. The element generates the progress, frame, reading, and caption inside it.
Generated classes
ClassTypeDescription
k-gauge--smmodifierSmall dial. Written from options.size.
k-gauge--lgmodifierLarge dial. Written from options.size.
k-gauge--infomodifierFill color. Also success, warning, and danger. Written from options.variant.
k-gauge--indeterminatemodifierBusy sweep. Written from options.indeterminate.
k-gauge__framepartThe square chrome around the reading. Decorative, aria-hidden.
k-gauge__segpartOne side of the frame. Combined with a track or fill layer and a side.
k-gauge__seg--trackmodifierThe empty track layer.
k-gauge__seg--fillmodifierThe filled layer. Length follows --k-gauge.
k-gauge__seg--leftmodifierThe left side of the frame.
k-gauge__seg--topmodifierThe top side of the frame.
k-gauge__seg--rightmodifierThe right side of the frame.
k-gauge__valuepartThe reading in the middle of the dial.
k-gauge__labelpartCaption in the open bottom. Hidden when empty.
OptionTypeDefaultDescription
valuenumber—How far along. Clamped to 0…max. Leave it out for an empty gauge.
maxnumber1The top of the range.
labelstring—Caption in the open bottom. Also labels the hidden progress.
formatstring—How to write the dial reading from value. Omit for a number. Pass "%" for a percent. Pass any other string ("$", "€", …) as a currency prefix.
textstring—Override the dial reading. Use when format is not enough. Leave it out to let format drive the dial.
variantinfo | success | warning | danger—Fill color. Leave it out for the primary orange chrome.
sizesm | lg—Dial size. Leave it out for the default.
indeterminateboolean—Busy sweep. Ignores value while set.

Every option above is also a property, so el.value = 80 works on its own and leaves the rest of the options alone. Three more are computed:

PropertyTypeDescription
percentnumberRead-only. Share of max the gauge shows, 0 to 100. An empty gauge reads 0.
isEmptybooleanRead-only. True when there is no value.
isCompletebooleanRead-only. True once value reaches max.
MethodReturnsDescription
increment(by)voidRaises value by one step, or by the amount given, stopping at max. Starts from 0 when empty.
decrement(by)voidLowers value the same way, stopping at 0.
complete()voidFills the gauge to max.
clear()voidDrops value so the gauge renders empty again.
getProgress()HTMLProgressElement | nullThe hidden progress element the gauge keeps in step.
refresh()voidRewrites the progress, reading, and caption from the current options.

Two imported helpers cover the setup cases:

HelperReturnsDescription
setGauge(el, value, max)voidSets value and max in one call. The reading follows format. max defaults to the current max; omit value to empty the gauge.
createGauge(options)KGaugeBuilds a k-gauge element from an options object, ready to append.

Unlike the other five elements, the gauge binds no listeners, so it has no disconnect(). Its methods also work on a gauge from createGauge() before it is appended, since it paints on demand rather than on connect.

import { createGauge, setGauge } from 'k-web-ui/js';
const gauge = createGauge({
value: 64,
max: 100,
label: 'Upload',
format: '%',
});
document.body.append(gauge);
setGauge(gauge, 80, 100);

The element writes the number and the caption. --k-gauge is how far the fill has gone along the three sides.

<k-gauge id="upload" class="k-gauge"></k-gauge>
import 'k-web-ui/js';
document.getElementById('upload').options = {
value: 64,
max: 100,
label: 'Upload',
format: '%',
};
import { Component, CUSTOM_ELEMENTS_SCHEMA, type AfterViewInit } from '@angular/core';
import 'k-web-ui/js';
@Component({
selector: 'app-example',
standalone: true,
schemas: [CUSTOM_ELEMENTS_SCHEMA],
template: `
<k-gauge id="upload" class="k-gauge"></k-gauge>
`,
})
export class ExampleComponent implements AfterViewInit {
ngAfterViewInit() {
document.getElementById('upload').options = {
value: 64,
max: 100,
label: 'Upload',
format: '%',
};
}
}
import { useEffect } from 'react';
import 'k-web-ui/js';
export function Example() {
useEffect(() => {
document.getElementById('upload').options = {
value: 64,
max: 100,
label: 'Upload',
format: '%',
};
}, []);
return (
<>
<k-gauge id="upload" className="k-gauge"></k-gauge>
</>
);
}

Leave format out for a number. Pass % for a percent. Pass any other string as a currency prefix. text is still there when you need a one-off like 12.4k.

<k-gauge id="open" class="k-gauge"></k-gauge>
<k-gauge id="balance" class="k-gauge"></k-gauge>
<k-gauge id="upload-pct" class="k-gauge"></k-gauge>
<k-gauge id="bandwidth" class="k-gauge"></k-gauge>
import 'k-web-ui/js';
document.getElementById('open').options = {
value: 8,
max: 100,
label: 'Open',
};
document.getElementById('balance').options = {
value: 2450,
max: 3000,
variant: 'success',
label: 'Balance',
format: '$',
};
document.getElementById('upload-pct').options = {
value: 64,
max: 100,
variant: 'info',
label: 'Upload',
format: '%',
};
document.getElementById('bandwidth').options = {
value: 12400,
max: 20000,
variant: 'info',
label: 'Bandwidth',
text: '12.4k',
};
import { Component, CUSTOM_ELEMENTS_SCHEMA, type AfterViewInit } from '@angular/core';
import 'k-web-ui/js';
@Component({
selector: 'app-example',
standalone: true,
schemas: [CUSTOM_ELEMENTS_SCHEMA],
template: `
<k-gauge id="open" class="k-gauge"></k-gauge>
<k-gauge id="balance" class="k-gauge"></k-gauge>
<k-gauge id="upload-pct" class="k-gauge"></k-gauge>
<k-gauge id="bandwidth" class="k-gauge"></k-gauge>
`,
})
export class ExampleComponent implements AfterViewInit {
ngAfterViewInit() {
document.getElementById('open').options = {
value: 8,
max: 100,
label: 'Open',
};
document.getElementById('balance').options = {
value: 2450,
max: 3000,
variant: 'success',
label: 'Balance',
format: '$',
};
document.getElementById('upload-pct').options = {
value: 64,
max: 100,
variant: 'info',
label: 'Upload',
format: '%',
};
document.getElementById('bandwidth').options = {
value: 12400,
max: 20000,
variant: 'info',
label: 'Bandwidth',
text: '12.4k',
};
}
}
import { useEffect } from 'react';
import 'k-web-ui/js';
export function Example() {
useEffect(() => {
document.getElementById('open').options = {
value: 8,
max: 100,
label: 'Open',
};
document.getElementById('balance').options = {
value: 2450,
max: 3000,
variant: 'success',
label: 'Balance',
format: '$',
};
document.getElementById('upload-pct').options = {
value: 64,
max: 100,
variant: 'info',
label: 'Upload',
format: '%',
};
document.getElementById('bandwidth').options = {
value: 12400,
max: 20000,
variant: 'info',
label: 'Bandwidth',
text: '12.4k',
};
}, []);
return (
<>
<k-gauge id="open" className="k-gauge"></k-gauge>
<k-gauge id="balance" className="k-gauge"></k-gauge>
<k-gauge id="upload-pct" className="k-gauge"></k-gauge>
<k-gauge id="bandwidth" className="k-gauge"></k-gauge>
</>
);
}

No value is an empty track. indeterminate is the busy sweep. prefers-reduced-motion: reduce stops the motion.

<k-gauge id="waiting" class="k-gauge"></k-gauge>
<k-gauge id="syncing" class="k-gauge"></k-gauge>
import 'k-web-ui/js';
document.getElementById('waiting').options = { max: 100, label: 'Waiting' };
document.getElementById('syncing').options = {
max: 100,
label: 'Syncing',
indeterminate: true,
};
import { Component, CUSTOM_ELEMENTS_SCHEMA, type AfterViewInit } from '@angular/core';
import 'k-web-ui/js';
@Component({
selector: 'app-example',
standalone: true,
schemas: [CUSTOM_ELEMENTS_SCHEMA],
template: `
<k-gauge id="waiting" class="k-gauge"></k-gauge>
<k-gauge id="syncing" class="k-gauge"></k-gauge>
`,
})
export class ExampleComponent implements AfterViewInit {
ngAfterViewInit() {
document.getElementById('waiting').options = { max: 100, label: 'Waiting' };
document.getElementById('syncing').options = {
max: 100,
label: 'Syncing',
indeterminate: true,
};
}
}
import { useEffect } from 'react';
import 'k-web-ui/js';
export function Example() {
useEffect(() => {
document.getElementById('waiting').options = { max: 100, label: 'Waiting' };
document.getElementById('syncing').options = {
max: 100,
label: 'Syncing',
indeterminate: true,
};
}, []);
return (
<>
<k-gauge id="waiting" className="k-gauge"></k-gauge>
<k-gauge id="syncing" className="k-gauge"></k-gauge>
</>
);
}

The element points aria-labelledby at the caption it writes, which is why the host needs an id. The reading is aria-hidden so the number is not read twice. The hidden <progress> is already a progressbar. Don’t add role="progressbar". prefers-reduced-motion: reduce stops the sweep.

Do

  • Give the host a unique id. The generated ids derive from it.
  • Set format so the reading follows value. Use text only for one-offs.
  • Use setGauge() when the value changes often.

Don’t

  • Use this when you only have a busy icon. That is spin.
  • Use this for a linear bar. That is progress.
  • Write k-gauge--success on the tag. That is variant: 'success', and the element writes the class.
  • Write the reading or the caption by hand.