kasperikoski/gaugestan-js

Prebuilt browser assets for the dependency-free Gaugestan.js SVG gauge instrument library.

Maintainers

Package info

github.com/kasperikoski/Gaugestan.js

Language:JavaScript

pkg:composer/kasperikoski/gaugestan-js

Transparency log

Statistics

Installs: 2

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.0 2026-07-28 19:40 UTC

This package is auto-updated.

Last update: 2026-07-28 19:56:13 UTC


README

Gaugestan.js is a lightweight, dependency-free, extensible analog gauge library built with native SVG, JavaScript, and a small optional CSS file.

Gaugestan.js includes eight gauge types:

  • arc: a clean semicircular gauge with configurable colored or transparent sectors;
  • classic: a framed, white, old-style instrument meter;
  • classic-linear: a cream-faced vintage instrument with a travelling linear indicator;
  • center-zero: a cream heritage bidirectional dial whose resting reference is centered at zero;
  • classic-linear-zero: the framed classic linear instrument with a fixed zero reference;
  • heritage-round: a circular vintage instrument built from the shared classic layers;
  • line-scale: a tick-focused gauge with independently configurable major and minor scale lines;
  • rolling-tape: a heritage aircraft-style moving number tape behind a fixed edge-to-edge reference line.

The runtime has no image, jQuery, canvas, or framework dependency. It can be used from a classic <script> tag, an ES module, a custom element, React, Twig, Blade, or another server-rendered application.

Current release: 1.0.0

Why SVG

SVG is used as the default renderer because it is resolution-independent, responsive without device-pixel-ratio bookkeeping, easy to theme with CSS, accessible through normal DOM attributes, and efficient for the relatively small number of vector elements used by analog gauges. Static parts are rendered once. During animation, Gaugestan.js normally updates only the active pointer, linear indicator, or rolling tape slots together with readout text and the ARIA value.

Browser installation with separate files

This is the recommended browser installation. It keeps JavaScript and CSS separately cacheable.

<link rel="stylesheet" href="gaugestan.min.css">
<div id="score-gauge"></div>
<script src="gaugestan.umd.min.js"></script>
<script>
  const gauge = Gaugestan.createGauge('#score-gauge', {
    type: 'arc',
    min: 0,
    max: 3000,
    value: 625,
    zones: [
      { min: 0, max: 500, color: '#ef4444' },
      { min: 500, max: 1000, color: '#facc15' },
      { min: 1000, max: 2100, color: '#22c55e' },
      { min: 2100, max: 2500, color: '#facc15' },
      { min: 2500, max: 3000, color: '#ef4444' }
    ]
  });

  gauge.setValue(1420);
</script>

Open example.html directly in a browser to see all eight built-in types, live controls, and a four-instrument customization gallery that replaces preset colors entirely through options.

One-file standalone browser installation

The standalone bundle exposes the same window.Gaugestan API and injects the complete responsive stylesheet automatically. The stable #gaugestan-styles marker ensures that the stylesheet is injected only once.

<div id="gauge"></div>
<script src="gaugestan.standalone.min.js"></script>
<script>
  Gaugestan.createGauge('#gauge', { type: 'classic', value: 72 });
</script>

The standalone and separate-file builds are generated from the same canonical src/gaugestan.css source. Neither contains a separately maintained CSS copy.

Native browser ES module

<link rel="stylesheet" href="./dist/gaugestan.css">
<div id="temperature"></div>
<script type="module">
  import Gaugestan, { createGauge } from './dist/gaugestan.esm.js';

  const gauge = createGauge(document.querySelector('#temperature'), {
    type: 'classic',
    min: -20,
    max: 120,
    value: 68,
    readout: {
      title: { visible: true, text: 'COOLANT' },
      unit: { visible: true, text: '°C' }
    }
  });

  console.log(Gaugestan.VERSION);
</script>

npm installation

npm install gaugestan.js@1.0.0
import { createGauge } from 'gaugestan.js';
import 'gaugestan.js/css';

const gauge = createGauge('#gauge', { type: 'arc', value: 64 });

npm consumers receive prebuilt JavaScript, CSS, declarations, and source maps. Applications normally do not modify anything in dist/.

CommonJS

const { createGauge } = require('gaugestan.js');
const gauge = createGauge(document.querySelector('#gauge'), { value: 64 });

CDN installation

Use exact versions in production. CDN consumers need no installation or compilation.

jsDelivr, recommended two-file installation

<link
  rel="stylesheet"
  href="https://cdn.jsdelivr.net/npm/gaugestan.js@1.0.0/dist/gaugestan.min.css"
>
<script
  src="https://cdn.jsdelivr.net/npm/gaugestan.js@1.0.0/dist/gaugestan.umd.min.js"
></script>

jsDelivr, one-file installation

<script
  src="https://cdn.jsdelivr.net/npm/gaugestan.js@1.0.0/dist/gaugestan.standalone.min.js"
></script>

UNPKG

<link
  rel="stylesheet"
  href="https://unpkg.com/gaugestan.js@1.0.0/dist/gaugestan.min.css"
>
<script
  src="https://unpkg.com/gaugestan.js@1.0.0/dist/gaugestan.umd.min.js"
></script>

The UNPKG one-file equivalent is https://unpkg.com/gaugestan.js@1.0.0/dist/gaugestan.standalone.min.js.

Built-in gauge gallery

Arc zones. A clean semicircular gauge with configurable colored sectors, tapered zones, and true transparent gaps.

Arc zones gauge with configurable colored sectors

Classic instrument. A framed white instrument with a glass highlight, fine scale markings, and a traditional spear pointer.

Classic framed analog instrument

Heritage round. A circular vintage instrument built from the shared classic face, bezel, scale, and pointer layers.

Heritage round analog instrument

Line scale. A tick-focused gauge with independently configurable major divisions, minor ticks, labels, and pointer styling.

Line scale gauge

Classic linear instrument. A cream-faced vintage scale with configurable zones, fine ticks, and a travelling hairline indicator.

Classic linear analog instrument

Center zero. A signed bidirectional dial with a fixed center reference and pointer movement on both sides of zero.

Center-zero bidirectional analog gauge

Classic linear center zero. The classic linear instrument with a permanent zero marker and signed indicator travel in both directions.

Classic linear center-zero instrument

Heritage rolling tape. An aircraft-style numeric tape that moves behind a fixed edge-to-edge reference line.

Heritage aircraft-style rolling tape instrument

Customization example

Rose balance. The heritage center-zero instrument recolored entirely through ordinary Gaugestan.js configuration options.

Rose-themed customization of the center-zero gauge

Minimal configuration

Only the target and value are required in ordinary use. Every gauge type supplies its own visual defaults.

const gauge = Gaugestan.createGauge('#gauge', {
  type: 'line-scale',
  value: 72
});

True transparent zones

A zone with color: 'transparent', color: 'none', or no color creates a real gap. The base track is not drawn under that interval unless track.showUnderZones is explicitly enabled.

zones: [
  { min: 0, max: 25, color: '#ef4444' },
  { min: 25, max: 40, color: 'transparent' },
  { min: 40, max: 80, color: '#22c55e' },
  { min: 80, max: 100, color: '#facc15' }
]

For compatibility with Gauge.js-style configuration, strokeStyle is accepted as an alias for a zone's color.

Radial zones may also taper smoothly. width remains the uniform fallback, while taper.startWidth and taper.endWidth define the thickness at the zone's numeric min and max endpoints. The direction therefore follows the value mapping even when invert is enabled:

zones: [
  {
    min: 0,
    max: 60,
    color: '#06b6d4',
    width: 18,
    taper: {
      startWidth: 0,
      endWidth: 36,
      segments: 56
    }
  }
]

segments controls curve smoothness and is clamped between 2 and 256. Tapered zones are rendered as filled SVG annular segments because a native SVG stroke cannot change width along one path. Uniform zones continue to use the lighter stroke representation.

Inverted scales

Set the top-level invert option to true to mirror the value direction while keeping the configured geometry unchanged. The minimum and maximum swap visual endpoints, and ticks, labels, zones, and the pointer all follow the same value-based mapping:

createGauge('#fuel', {
  type: 'heritage-round',
  min: 0,
  max: 100,
  invert: true
});

invert defaults to false. Zones remain defined with their normal numeric min and max values; low-value warning zones therefore move automatically with the inverted scale and do not need to be rewritten.

Major and minor ticks

scale: {
  radius: 124,
  position: 'inside', // inside, outside, or cross
  major: {
    interval: 20,     // explicit value interval
    divisions: 5,     // used only when interval is null
    length: 24,
    width: 2.5,
    color: '#111827'
  },
  minor: {
    subdivisions: 4, // four minor lines between major ticks
    length: 11,
    width: 1,
    color: '#64748b'
  }
}

Pointer types

Built-in pointer shapes are:

  • needle: traditional tapered needle;
  • line: rounded line pointer;
  • arrow: arrow-shaped pointer;
  • spear: vintage instrument pointer.
pointer: {
  type: 'spear',
  length: 98,
  shaftLength: 67, // optional; null preserves the type-specific shape
  tailLength: 14,
  width: 9,
  color: '#111827',
  colorByZone: false,
  cap: {
    visible: true,
    stackOrder: 'above', // above or below the pointer shape
    radius: 9,
    color: '#20242a',
    strokeColor: '#ffffff',
    strokeWidth: 2
  }
}

Built-in gauge types paint their pointer or linear indicator after the readout, so it always crosses above title, value, and unit text. Caps default to stackOrder: 'above'. A custom type can use 'below' when its pointer should cross over the center cap. This uses deterministic SVG paint order rather than CSS z-index.

Classic linear instrument

classic-linear reuses the classic cream panel, bezel, glass, readouts, scale configuration, zones, animation, jitter, and accessibility lifecycle. Its value moves a straight indicator instead of rotating a pointer:

createGauge('#signal', {
  type: 'classic-linear',
  min: 0,
  max: 100,
  value: 62,
  linear: {
    originY: 142,
    startX: 38,
    startY: 0,
    endX: 282,
    endY: 0,
    tickSide: 'negative',
    labelOffset: 38,
    zoneOffset: -16
  },
  indicator: {
    type: 'hairline', // hairline | marker | carriage
    position: 'cross',
    length: 84,
    centerOffset: 0, // positive moves upward on a horizontal axis
    width: 2.4,
    cap: { visible: false }
  },
  zones: [
    { min: 0, max: 20, color: '#b91c1c' },
    { min: 20, max: 70, color: 'transparent' },
    { min: 70, max: 100, color: '#15803d' }
  ]
});

The axis is defined by two points, so custom types may use horizontal, vertical, or diagonal linear scales. Linear Y coordinates use a Cartesian convention around linear.originY: 0 is the preset baseline, positive values move upward, and negative values move downward. X coordinates continue to increase to the right. The built-in classic-linear fallback therefore uses startY: 0, endY: 0, and title y: 60. labelOffset, zoneOffset, and indicator.centerOffset use the same positive-up convention for a left-to-right horizontal scale. invert: true reverses value direction without changing axis or zone definitions. Major and minor tick lengths remain independently configurable through scale.major.length and scale.minor.length.

Center-zero and rolling-tape gauges

The specialized built-ins use the same lifecycle, animation, jitter, zones, accessibility, responsive layout, and runtime update API as the other instruments.

The cream-faced radial center-zero preset keeps an automatically symmetric range around a custom reference value. Its title is an ordinary readout part and defaults between the pointer hub and the lower numeric value; readout.title.x and readout.title.y can move it anywhere in the dial:

createGauge('#trim', {
  type: 'center-zero',
  min: -50,
  max: 50,
  value: 12,
  centerZero: {
    zeroValue: 0,
    symmetric: true,
    marker: { color: '#5b4636', length: 26, width: 3 }
  }
});

For a straight scale, classic-linear-zero reuses the complete classic linear face, axis, ticks, labels, zones, indicator, clipping, and sizing system. It adds only a fixed zero-reference layer:

createGauge('#deviation', {
  type: 'classic-linear-zero',
  min: -100,
  max: 100,
  value: -28,
  geometry: { width: 520, height: 220 },
  centerZero: {
    zeroValue: 0,
    symmetric: true,
    marker: { visible: true, length: 34, centerOffset: 0 }
  }
});

rolling-tape keeps an edge-to-edge reference line fixed while the numerical scale moves smoothly underneath it. Its cream face is only a preset: every face, window, strip, tick, label, fade, reference-line, and zone color remains configurable.

createGauge('#altitude', {
  type: 'rolling-tape',
  geometry: { width: 300, height: 480 },
  min: 0,
  max: 10000,
  value: 4250,
  tape: {
    interval: 2000,
    minorSubdivisions: 3,
    majorSpacing: 58,
    stripColor: '#fff9ea',
    textColor: '#211f1a',
    indicatorColor: '#991b1b',
    windowInnerStrokeColor: '#d8cfbd'
  },
  readout: {
    title: { visible: true, text: 'ALTITUDE' },
    value: { visible: true, x: 150, y: 390 },
    unit: { visible: true, text: 'FT' }
  }
});

The optional separate tape value uses the same independently configurable x and y coordinates as every other readout part. It may be hidden when the moving tape itself is the only numeric display required.

All three presets accept normal updateOptions() calls, invert, custom formatters, custom CSS classes, and per-zone colors. Omitting an option always falls back to the type preset.

Warning lights and annunciators

Every built-in gauge type can host the shared optional light layer. The lamp is SVG, so its coordinates and radius scale with the gauge viewBox, it respects framed face clipping, and it remains correctly positioned at every rendered size. The component is disabled by default.

createGauge('#fuel', {
  type: 'heritage-round',
  min: 0,
  max: 100,
  value: 12,
  light: {
    visible: true,
    x: 93,
    y: 110,
    radius: 8,
    glowRadius: 20,
    glowSharpness: 0,
    glowOpacity: 0.15,
    color: 'red',
    offColor: '#f3ead6',
    pulse: false,
    blink: false,
    blinkInterval: 1000,
    flicker: true,
    flickerIntensity: 0.1,
    flickerGlowRadius: 0.1,
    flickerMinInterval: 45,
    flickerMaxInterval: 140,
    trigger: { mode: 'below', source: 'target', value: 15 }
  }
});

Built-in palettes are red, green, blue, amber, yellow, orange, white, halogen, cyan, and brown. Use customColor for an application-specific active color. The unlit lamp deliberately uses a softly diffused cream lens rather than a black bulb; offColor, offEdgeColor, bezelColor, and bezelHighlightColor can customize that hardware appearance. radius controls the physical lamp while glowRadius controls the halo independently and defaults to 20. Set it to null to derive the halo radius from radius * 1.82. glowSharpness controls the halo blur: 0 uses a soft Gaussian glow, while 1 removes blur for a completely sharp edge. Intermediate values reduce the blur continuously. glowOpacity controls only the halo and defaults to 0.15, independently from the illuminated lens opacity. The halo is painted below the opaque lamp hardware, so it becomes visible beyond the lamp edges instead of covering the lens. pulse adds a deliberately subtle steady-light breathing effect. blink alternates the complete illuminated and unlit lens states, so the dark half-cycle looks exactly like the normal switched-off lamp rather than a faded transparent bulb. Blink takes precedence if both blink and pulse are enabled. flicker adds rapid random changes to the amount of emitted light. flickerIntensity sets the maximum random loss from full light: 0 is steady and 1 may momentarily reach the real unlit-lens appearance. It never darkens the lit lens toward black. flickerGlowRadius independently varies the halo radius by a relative plus/minus amount: with glowRadius: 20, the default 0.1 produces radii between 18 and 22. Each change waits a random duration between flickerMinInterval and flickerMaxInterval. Flicker is independent and may be combined with either pulse or blink. Flicker is enabled by default, but has no visual effect while the lamp is hidden or inactive. Trigger modes are manual, below, above, and between. The trigger source defaults to target, so a low-value lamp does not flash merely because a startup animation begins at the minimum. Use displayed to follow the animated readout value or pointer to include visual jitter. For linear gauges, light.y follows the same positive-up coordinate convention as the linear axis.

opacity controls only the illuminated lens layered over an always-opaque unlit lens. The physical lamp and bezel therefore remain fully visible even at opacity: 0; glowOpacity remains the separate control for the halo.

Custom types can opt in with Gaugestan.layers.light; all built-in types already include the layer and use the same shared light defaults. The lamp remains disabled until light.visible is true. The interactive example.html explicitly enables blink and disables flicker for its heritage-round demonstration.

Intrinsic dimensions and host sizing

classic-linear, classic-linear-zero, and rolling-tape can change their internal instrument proportions through geometry.width and geometry.height. The type's configuration hook scales only fallback geometry; any explicitly supplied coordinate remains authoritative. geometry.aspectRatio may derive the missing dimension:

createGauge('#wide-signal', {
  type: 'classic-linear',
  geometry: { width: 560, aspectRatio: 2.8 }
});

These logical dimensions control composition and the SVG viewBox. Page layout can still size any gauge host independently with normal CSS. Gaugestan provides optional custom properties for common integrations:

#wide-signal {
  --gaugestan-width: 100%;
  --gaugestan-height: 220px;
  --gaugestan-aspect-ratio: 14 / 5;
}

Use layout.mode: 'contain' when both host width and height are constrained and the whole instrument must scale inside that box. Leave the default intrinsic mode when the SVG's natural aspect ratio should determine document height.

Optional readout text

The title, numeric value, and unit are independent SVG text elements. The numeric value and unit are hidden by default so a gauge starts as a pure analog instrument. x: 'auto' centers a part in its instrument reference box. y: 'auto' uses position: 'top', center, or bottom, with optional margin, offsetX, and offsetY. Offsets refine automatic placement only. Numeric coordinates are final SVG positions for radial gauges. In linear gauges, numeric Y coordinates are relative to linear.originY and increase upward, while numeric X coordinates increase rightward:

readout: {
  visible: true,
  title: { visible: true, text: 'PRESSURE', x: 160, y: 24 },
  value: { visible: true, x: 160, y: 50, fractionDigits: 0 },
  unit: { visible: true, text: 'kPa', x: 160, y: 72 }
}

For the built-in arc type, the title defaults to x: 'auto', y: 35. An enabled numeric value defaults to x: 160, y: 130, inside the arc between the upper scale labels and the pointer pivot.

Custom arc sweep and responsive layout

geometry.startAngle and geometry.endAngle define the pointer and scale sweep. For example, a centered 240-degree arc uses startAngle: 150 and endAngle: 390. Interactive controls can use Gaugestan.geometry.centeredArcAngles(sweep, centerAngle): a center angle of 0 means the normal upright orientation (internally SVG angle 270), while positive values rotate clockwise. The difference may be configured up to 360 degrees. Full circles are rendered as two SVG half-arcs, and the maximum tick/label endpoint is deduplicated by default so it does not overlap the minimum endpoint.

The default layout.mode: 'intrinsic' computes a content-aware viewBox. Increasing a sweep to 360 degrees therefore increases the gauge's natural height and pushes the following document content down instead of overflowing it. Use layout.mode: 'contain' when the host has an explicit CSS width and height and the complete instrument should scale down to fit that box:

layout: {
  mode: 'contain', // intrinsic | contain
  padding: 10
}

Classic panel faces use face.fit: 'content', so the face expands when a larger sweep or moved readout needs more drawing space. face.minWidth and face.minHeight define optional lower limits for a content-fitted frame. The built-in framed instruments also use face.clipContent: true, which clips pointers, indicators, zones, ticks, labels, and readouts to the inner bezel instead of allowing them to overflow the frame. Circular faces remain available through shape: 'circle' and the ready-made heritage-round type.

Startup animation

animation: {
  enabled: true,
  duration: 900,
  easing: 'easeOutCubic',
  animateOnInit: true,
  startFrom: 'min', // min, max, value, or a number
  respectReducedMotion: true
}

Set startFrom: 'value' or animateOnInit: false when the pointer must already be at its final position on first paint.

Realistic pointer jitter

Jitter is a pointer-like visual effect. It randomizes the radial pointer angle or linear indicator position without changing the readout, getValue(), getDisplayedValue(), accessibility value, or application data.

effects: {
  jitter: {
    enabled: true,
    amplitude: 0.8,
    amplitudeUnit: 'value', // value or percent
    frequency: 5,
    smoothing: 0.88,
    whenIdleOnly: true
  }
}

Animation and jitter automatically pause when the page is hidden. They also pause when the gauge is offscreen when IntersectionObserver is available.

Updating an instance

gauge.setValue(76);
gauge.setValue(18, { duration: 1400, easing: 'easeInOutCubic' });
gauge.setValue(50, { animate: false });

gauge.updateOptions({
  pointer: { type: 'line', color: '#dc2626' },
  labels: { fractionDigits: 1 }
});

// Replace the complete instance configuration and discard earlier overrides.
gauge.replaceOptions({ type: 'classic', min: 0, max: 100 });

gauge.pause();
gauge.resume();
gauge.refresh();
gauge.destroy();

set(value) and setOptions(options) are convenience aliases. updateOptions() merges a partial patch, while replaceOptions() replaces the complete user-option object and is useful for declarative framework adapters.

Data-attribute initialization

<div
  data-gaugestan
  data-gaugestan-type="arc"
  data-gaugestan-min="0"
  data-gaugestan-max="100"
  data-gaugestan-value="63"
  data-gaugestan-options='{"animation":{"startFrom":"value"}}'
></div>

<script src="dist/gaugestan.umd.min.js"></script>
<script>
  Gaugestan.autoMount();
</script>

Auto mounting is duplicate-safe.

Custom element

<script src="dist/gaugestan.umd.min.js"></script>
<script>Gaugestan.defineGaugestanElement();</script>

<gaugestan-gauge
  type="classic"
  min="0"
  max="240"
  value="88"
  options='{"readout":{"title":{"visible":true,"text":"VOLTAGE"}}}'
></gaugestan-gauge>

The value property or attribute can be changed later.

With npm, the same API is available through the explicit Web Component subpath:

import { defineGaugestanElement } from 'gaugestan.js/web-component';
import 'gaugestan.js/css';

defineGaugestanElement();

React

import { useState } from 'react';
import Gaugestan from 'gaugestan.js/react';
import 'gaugestan.js/css';

export default function DashboardGauge() {
  const [value, setValue] = useState(62);

  return (
    <Gaugestan
      value={value}
      options={{
        type: 'arc',
        min: 0,
        max: 100,
        zones: [
          { min: 0, max: 40, color: '#ef4444' },
          { min: 40, max: 75, color: '#facc15' },
          { min: 75, max: 100, color: '#22c55e' }
        ]
      }}
    />
  );
}

The React adapter is intentionally thin. React owns the host node and Gaugestan.js owns only the generated content inside it. The published gaugestan.js/react entry is normal browser-ready ESM JavaScript and does not require a package consumer to transpile JSX inside node_modules. It replaces the complete options prop when that prop changes, keeps an explicit value prop authoritative, destroys the instance on unmount, and forwards a ref to the live Gauge instance.

Laravel, Twig, or plain PHP

Copy the prebuilt files into a public vendor directory, include the CSS and UMD bundle, and initialize from the page module. No PHP integration is required.

A Blade example is included in examples/laravel-blade.blade.php.

Composer distribution

The package includes Composer metadata and a cross-platform vendor binary.

composer require kasperikoski/gaugestan-js:^1.0
vendor/bin/gaugestan-install-assets public/assets/vendor/gaugestan

The default command copies the recommended gaugestan.umd.min.js and gaugestan.min.css. Use --standalone for the one-file build, --all for all browser assets, or --clean to remove only known old Gaugestan.js assets before copying. Unrelated destination files are never removed.

Composer consumers receive prebuilt assets and do not need Node.js, npm, esbuild, or a frontend compilation step. A consuming project can automate the explicit asset command in its own composer.json:

{
  "scripts": {
    "assets:gaugestan": "vendor/bin/gaugestan-install-assets public/assets/vendor/gaugestan",
    "post-install-cmd": [
      "@assets:gaugestan"
    ],
    "post-update-cmd": [
      "@assets:gaugestan"
    ]
  }
}

The dependency itself deliberately does not write into an application's public directory during Composer installation.

For server-rendered applications, install the assets into a versioned public vendor path, load them through the application's asset helper, and initialize gauges from a reusable module or duplicate-safe data-attribute initializer.

Extending gauge types

A type is a name, a defaults object, and an ordered array of layers. A layer renders static SVG and may return a small controller whose update(value, state) method is called for dynamic changes.

Gaugestan.registerGaugeType('football-score', {
  description: 'A compact score-performance meter.',
  defaults: {
    geometry: { startAngle: 200, endAngle: 340 },
    pointer: { type: 'arrow', color: '#0f172a' },
    readout: { title: { visible: true, text: 'FORM' } }
  },
  layers: [
    Gaugestan.layers.face,
    Gaugestan.layers.zones,
    Gaugestan.layers.ticks,
    Gaugestan.layers.labels,
    Gaugestan.layers.pointer,
    Gaugestan.layers.readout
  ]
});

Custom layers can add SVG decorations, markers, secondary pointers, trend arrows, warning lights, or a completely different visual language without changing the animation core.

See docs/creating-gauge-types.md for a complete custom layer example.

Accessibility

The generated root uses role="meter" and receives aria-valuemin, aria-valuemax, and aria-valuenow. Provide a meaningful label and optional value text:

accessibility: {
  label: 'Prediction accuracy',
  description: 'Accuracy across the last 50 predictions.',
  valueText: (value) => `${Math.round(value)} percent accurate`
}

Color should not be the only way to communicate meaning. Keep labels, text, or surrounding explanations available for important status information.

Browser support

Gaugestan.js targets current evergreen browsers with native SVG, ES2018+, requestAnimationFrame, and modern DOM APIs. The UMD build works as a classic script. The ESM build works in modern module-aware browsers and bundlers.

Optional optimizations degrade safely:

  • no IntersectionObserver: the gauge continues running while offscreen;
  • no matchMedia: reduced-motion detection is skipped;
  • no customElements: only the optional web-component adapter is unavailable.

Build and tests

Maintainers edit files in src/, adapters, tests, documentation, and build scripts. Install the locked development toolchain and regenerate every distribution format:

npm ci
npm run clean
npm run build
npm run dev
npm run typecheck
npm test
npm run test:browser
npm run check
npm pack --dry-run
npm publish --dry-run

npm run build uses esbuild to recreate the complete dist/ directory, including normal and minified ESM, UMD, CommonJS, standalone bundles, source maps, CSS, declarations, and the manifest. npm run dev watches source files. npm run check starts from a clean directory and runs the build, declaration validation, automated tests, DOM smoke test, npm package-content dry run, and Composer validation when Composer is available. prepublishOnly runs the same full check.

Minified files, source maps, embedded standalone CSS, declarations in dist/, and other generated files must never be edited manually. Change their canonical sources and run npm run build.

The optional npm run test:chromium command starts a temporary local HTTP server and uses a globally available Chromium executable. It is intentionally separate from the portable default checks.

Distribution policy

dist/ is intentionally committed. GitHub source downloads, Composer installations, and CDN/npm consumers receive ready-to-use browser files without requiring Node.js. CI runs a clean build and can detect differences in the committed output. Package users receive prebuilt assets; only maintainers run the distribution build.

Package layout

Gaugestan.js/
├── src/                  Canonical modular source and CSS
├── dist/                 Reproducible, committed distribution files
├── adapters/react/       Optional React adapter and declarations
├── docs/                 Architecture, API, and development documentation
├── examples/             Framework and custom-type examples
├── tests/                Unit, DOM, packaging, and installer tests
├── scripts/              Build and local validation scripts
├── bin/                  Composer asset installer
└── example.html          Interactive standalone demonstration

Release metadata

Version: 1.0.0. npm package: gaugestan.js. Composer package: kasperikoski/gaugestan-js. Repository: https://github.com/kasperikoski/Gaugestan.js.

Build and CI workflows validate release artifacts. Publishing to GitHub, npm, and Packagist is a manual maintainer action.

Contributing and security

Contribution setup and pull-request requirements are documented in CONTRIBUTING.md. Report suspected vulnerabilities privately as described in SECURITY.md; do not disclose them in public issues.

License

MIT License. See LICENSE.