Skip to content

Allocation Meter

Split a fixed total across categories and move amounts between them, such as budget envelopes.

import { AllocationMeter, Envelope } from '@youj/react'

Installation & usage

npm i @youj/react

For an app already set up with npx @youj/cli init, which installs the peers (@youj/core, @youj/motion, motion and Base UI) and wires the provider. New app? Start at Installation.

Use an allocation meter for a budget split into pots that each have their own limit: groceries, eating out, transport. Each envelope is a column of cells holding what's left, draining from the top as money is spent. Money rolled over from last period sits on top, hatched, and goes first; an envelope spent past empty turns its base the danger tone and drips under it, one drop for each tenth it's over, up to three. Money moves by dragging one envelope onto another, or from the keyboard. The Budget block builds a month's budget around it. For spend against one limit with policy lines, use an Limit Meter; for how full several resources are, a Segmented Meter.

import { useState } from 'react'
import { AllocationMeter, type Envelope } from '@youj/react'

export function Budget() {
  const [envelopes, setEnvelopes] = useState<Envelope[]>([
    { id: 'groceries', name: 'Groceries', budget: 520, spent: 402, rollover: 40 },
    { id: 'eating', name: 'Eating out', budget: 220, spent: 252 },
    { id: 'fun', name: 'Fun', budget: 160, spent: 71 },
  ])
  const move = (from: string, to: string, amount: number) =>
    setEnvelopes((list) => list.map((e) => (e.id === from ? { ...e, budget: e.budget - amount } : e.id === to ? { ...e, budget: e.budget + amount } : e)))

  return <AllocationMeter envelopes={envelopes} onMove={move} step={10} />
}

An envelope is its name, this period's budget, what's been spent, and any rollover carried in; what's left is the budget plus the rollover, less the spending. icon puts a glyph beside the name, and tone sets its color (the chart colors in turn by default). An envelope under 15% turns the warning tone, and one past empty the danger tone. Under the rack, a readout names the envelope in focus or under the pointer and what it has left, beside the rollover and overspent swatches; legend={false} leaves that row out.

The meter is controlled. Give onMove and money can move: it's called with the source's id, the target's id and the amount, and you update the budgets. Without it the meter is read-only. step is what one drag or one Up press carries (10 by default), and a move never takes more than the source has left. segments is the cells per envelope (16), and format the Intl.NumberFormat options for the amounts (whole US dollars by default).

Reference

Propsenvelopes, onMove, step… (9)

Props

AllocationMeter props
PropType
envelopesRequiredEnvelope[]–
onMove(from: string, to: string, amount: number) => void

Money moved from one envelope to another; update the budgets. Without it nothing moves.

–
stepnumber

What one drop or one Up press carries.

default 10

10
segmentsnumber

Cells per envelope.

default 16

16
formatIntl.NumberFormatOptions

Intl.NumberFormat options for the amounts. Default: US dollars, whole.

–
localestring

Default: the YoujProvider locale.

–
labelstring

The rack's accessible name.

default "Envelopes"

"Envelopes"
soundboolean

With sound on, a drop plays select.

default true

true
legendboolean

The row under the rack: a readout of the envelope in focus or under the pointer, and the rollover and overspent swatches.

default true

true
Other props pass through: extends ComponentProps<'div'>.
StylingParts and data attributes

Styling

Parts: allocation-meter (with data-carrying), allocation-meter-envelope (with data-state: ok, low or over, and data-source and data-target while money is in hand), allocation-meter-value, allocation-meter-track, allocation-meter-cell (with data-state and data-rollover), allocation-meter-base, allocation-meter-spill, allocation-meter-label, allocation-meter-ghost, allocation-meter-carry, allocation-meter-packet, and under the rack allocation-meter-rack, allocation-meter-footer, allocation-meter-readout and allocation-meter-legend. An envelope's color is --envelope-tone.

MotionPick up, drop, relight… · reduced motion

Motion

Motion tokens
MomentTokens
Pick upthe cells the amount would take dim in the source, and a ghost of the cells it would add rings the target
duration.fastease.standard

140ms · cubic-bezier(0.2, 0, 0, 1)

140mscubic-bezier(0.2, 0, 0, 1)
Dropa chip carries the amount across in a low arc
duration.slowease.emphasized

340ms · cubic-bezier(0.6, 0, 0.1, 1)

340mscubic-bezier(0.6, 0, 0.1, 1)
Relightboth columns relight from their old edge, one cell at a time
duration.deliberateease.standard

560ms · cubic-bezier(0.2, 0, 0, 1)

560mscubic-bezier(0.2, 0, 0, 1)
Overspendthe drops under the base draw out one after another
duration.baseease.enter

220ms · cubic-bezier(0.16, 1, 0.3, 1)

220mscubic-bezier(0.16, 1, 0.3, 1)

Reduced motion

Cells light at once, no chip travels and the drops appear in place.

Reduced motion

Cells light and darken at once, the chip doesn't travel on a drop, and an overspent envelope's drops appear in place.

AccessibilityKeyboard, focus, announcements

Accessibility

  • The rack is a group named by label (default: the labels' "Envelopes"), and with onMove described by how to move money from the keyboard.
  • Each envelope is a button named by what it has left, of what, and any rollover ("Groceries, $158 left of $560, $40 rolled over"), or by how far over it is ("Eating out, $32 over").
  • The envelopes are one tab stop. Left and right move between them (mirrored in RTL), Home and End go to the ends. Space or Enter picks money up; then the arrows choose where it goes, Up and Down change the amount by step, Enter moves it and Escape cancels. Moving focus out of the rack cancels too.
  • Every step is announced in a polite live region: what's picked up, where it would go, what moved and a cancel.
  • Its words come from the labels (envelopes, envelopeLeft, envelopeOver, envelopeOf, envelopeRollover, envelopeRolloverTag, envelopeOverspentTag, envelopeHint, envelopePickedUp, envelopeTarget, envelopeMoved and envelopeMoveCancelled), translated by the language packs.