M Majid DS mds
GitHub

mds components

mds:chart

Monochrome dashboard charts, server-rendered as SVG — an open answer to a Flux Pro component.

A family of dashboard visualizers in one ink: rounded splines, pill bars, arc dials. Everything renders on the server — no chart library, no JSON payload, no script. The design follows Amicro's Mono Charts (MIT). Flux Pro's flux:chart is a client-side line-chart toolkit; mds:chart is a different, batteries-included take for static dashboard cards.

Spline dynamics Line
84 k nodes
0 25 50 75 100 Jan Feb Mar Apr May Jun
Rounded caps 84k peak
<mds:chart
    label="Spline dynamics"
    badge="Line"
    :value="84"
    unit="k nodes"
    footer-start="Rounded caps"
    footer-end="84k peak"
    class="w-full max-w-sm"
>
    <mds:chart.line :data="[24, 45, 38, 65, 52, 84]" :labels="['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun']" />
</mds:chart>

The card and the stage

Two layers, both optional. mds:chart is the card — label, badge, big stat, footer. The mds:chart.* stages draw the actual chart and work anywhere on their own: inside a flux:card, a table cell, or bare. The ink is currentColor, so one text utility recolors a whole chart.

0 25 50 75 100 02 06 10 14 18 22
<div class="w-full max-w-sm space-y-4">
    <mds:chart.line :data="[18, 34, 72, 89, 64, 48]" :labels="['02', '06', '10', '14', '18', '22']" area :dots="false" />
    <mds:chart.line :data="[18, 34, 72, 89, 64, 48]" area :dots="false" :axis="false" class="text-accent" />
</div>

Line

The line is a monotone spline — smooth, but it never overshoots the data. baseline adds a dashed comparison series on the same axis, area pours a soft gradient under the curve, and curve="straight" switches to plain segments.

Weekly sessions vs last week
84 k
0 25 50 75 100 Sat Sun Mon Tue Wed Thu
Dashed: last week 84k peak
<mds:chart
    label="Weekly sessions"
    badge="vs last week"
    :value="84"
    unit="k"
    footer-start="Dashed: last week"
    footer-end="84k peak"
    class="w-full max-w-sm"
>
    <mds:chart.line
        :data="[24, 45, 38, 65, 52, 84]"
        :baseline="[18, 32, 29, 48, 41, 62]"
        :labels="['Sat', 'Sun', 'Mon', 'Tue', 'Wed', 'Thu']"
        area
    />
</mds:chart>

Bars

Bars are pills — fully rounded ends, one tone. A secondary series renders as a faint twin next to each bar. When an item is an array, its values stack: solid base, lighter tones upward, only the stack's outer ends rounded.

Pill pillars Grouped
422 units
0 25 50 75 100 Mon Tue Wed Thu Fri Sat
Stacked tones 3 layers
160 cumulative
0 50 100 150 200 Q1 Q2 Q3 Q4
<div class="grid w-full gap-4 sm:grid-cols-2">
    <mds:chart label="Pill pillars" badge="Grouped" :value="422" unit="units">
        <mds:chart.bars
            :data="[45, 78, 62, 95, 88, 54]"
            :secondary="[25, 40, 30, 55, 50, 28]"
            :labels="['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']"
        />
    </mds:chart>

    <mds:chart label="Stacked tones" badge="3 layers" :value="160" unit="cumulative">
        <mds:chart.bars
            :data="[[30, 25, 20], [45, 35, 25], [60, 40, 30], [75, 50, 35]]"
            :labels="['Q1', 'Q2', 'Q3', 'Q4']"
        />
    </mds:chart>
</div>

Funnel rows

horizontal switches to HTML rows — label, pill track, value. Rows follow the page direction (they grow from the right in RTL), which makes them the funnel of choice for Persian checkouts.

Stage funnel Pipeline
24% conversion
Visits 100
Signup 68
Active 42
Paid 24
<mds:chart label="Stage funnel" badge="Pipeline" value="24%" unit="conversion" class="w-full max-w-sm">
    <mds:chart.bars horizontal :data="[100, 68, 42, 24]" :labels="['Visits', 'Signup', 'Active', 'Paid']" />
</mds:chart>

Donut

Rounded segments separated by real gaps, shaded down a fixed tone ladder in segment order. The keys of data are the labels; the legend inherits each segment's tone. value and label fill the center.

Rounded donut Soft arc caps
100% allocation
100% Mono arc
Core engine UI layer Assets Other
<mds:chart label="Rounded donut" badge="Soft arc caps" value="100%" unit="allocation" class="w-full max-w-sm">
    <mds:chart.donut
        :data="['Core engine' => 45, 'UI layer' => 30, 'Assets' => 15, 'Other' => 10]"
        value="100%"
        label="Mono arc"
    />
</mds:chart>

Gauge and bullet

The gauge is a 240° dial with pill ends over a faint track. The bullet rows compare a value against a target — the marker is the one accent-colored element in the family, sitting on --color-accent.

Speedometer arc Gauge
84% performance
84 Target met
Bullet target Benchmark
3 targets
Throughput 82% / 75%
Latency 65% / 80%
Uptime 95% / 90%
<div class="grid w-full gap-4 sm:grid-cols-2">
    <mds:chart label="Speedometer arc" badge="Gauge" value="84%" unit="performance">
        <mds:chart.gauge :value="84" label="Target met" />
    </mds:chart>

    <mds:chart label="Bullet target" badge="Benchmark" :value="3" unit="targets">
        <mds:chart.bullet :items="[
            ['label' => 'Throughput', 'value' => 82, 'target' => 75],
            ['label' => 'Latency', 'value' => 65, 'target' => 80],
            ['label' => 'Uptime', 'value' => 95, 'target' => 90],
        ]" />
    </mds:chart>
</div>

Radar

A polygon web: four grid rings, one spoke per key, the shape filled at 15% ink.

Polygon web Radar
85 score
Speed Memory Scale Latency IOPS
<mds:chart label="Polygon web" badge="Radar" :value="85" unit="score" class="w-full max-w-sm">
    <mds:chart.radar :data="['Speed' => 90, 'Memory' => 75, 'Scale' => 85, 'Latency' => 95, 'IOPS' => 80]" />
</mds:chart>

Activity heatmap

A contribution grid: values grade into five tones of the ink, columns follow the page direction, and hovering a tile reports its value in the callout row (Alpine, with a title fallback). color="accent" swaps the ladder onto the accent color.

Activity heatmap 14 weeks
807 contributions
Jan Feb Mar
Hover tiles for details
<mds:chart label="Activity heatmap" badge="14 weeks" :value="807" unit="contributions" class="w-full max-w-md">
    <mds:chart.heatmap
        :data="array_map(fn ($i) => ($i * 7) % 13, range(1, 98))"
        :labels="['Jan', 'Feb', 'Mar']"
        color="accent"
    />
</mds:chart>

KPI card recipe

There is no KPI component — it is the card doing what it already does: a stat, a delta (a leading minus turns it red), and a bare mds:chart.sparkline, which also works inline in tables and lists.

KPI stat card Metric
$48,920 +14.2%
Rounded sparkline Monthly revenue
<mds:chart label="KPI stat card" badge="Metric" value="$48,920" delta="+14.2%" footer-start="Rounded sparkline" footer-end="Monthly revenue" class="w-full max-w-sm">
    <mds:chart.sparkline :data="[30, 45, 35, 60, 50, 85, 75, 95]" area class="h-16" />
</mds:chart>

Persian output

config('mds.persian_digits') — on by default in a real app, off in these docs — switches every digit the family renders: the card's stat and delta, axis ticks, bullet readouts, the heatmap's callout and its built-in hover hint. :fa="true" on the card flows into the stages inside it. The SVG plot plane itself never mirrors; the HTML stages (funnel rows, bullet, heatmap) follow the page direction.

فروش ماهانه تومان
۴۸٬۹۲۰ هزار +۱۴.۲%
۰ ۲۵ ۵۰ ۷۵ ۱۰۰ فروردین اردیبهشت خرداد تیر مرداد شهریور
شش ماه اخیر اوج در مرداد
قیف فروش ۴ مرحله
۲۴٪ نرخ تبدیل
بازدید ۱۰۰
سبد خرید ۶۸
پرداخت ۴۲
خرید ۲۴
<div class="grid w-full gap-4 sm:grid-cols-2">
    <mds:chart label="فروش ماهانه" badge="تومان" :value="48920" unit="هزار" delta="+14.2%" footer-start="شش ماه اخیر" footer-end="اوج در مرداد" :fa="true">
        <mds:chart.line :data="[24, 45, 38, 65, 52, 84]" :labels="['فروردین', 'اردیبهشت', 'خرداد', 'تیر', 'مرداد', 'شهریور']" area />
    </mds:chart>

    <mds:chart label="قیف فروش" badge="۴ مرحله" value="۲۴٪" unit="نرخ تبدیل" :fa="true">
        <mds:chart.bars horizontal :data="[100, 68, 42, 24]" :labels="['بازدید', 'سبد خرید', 'پرداخت', 'خرید']" />
    </mds:chart>
</div>

Reference

mds:chart

PropDescription
labelUppercase kicker line above the stat.
badgeSmall pill next to the label.
valueThe big stat. Numbers get thousands separators; strings ("84%") keep their shape with localized digits.
unitFaint suffix after the stat.
deltaChange readout after the stat — green, or red when it starts with a minus.
footer-startMuted footer text on the leading edge.
footer-endEmphasized footer text on the trailing edge.
faPersian digits for the stat and delta — inherited by the stages inside. Default: config('mds.persian_digits').
SlotDescription
headerExtra content on the header's trailing edge — filters, a toggle.
footerReplaces the two footer props.
defaultThe stage(s).

mds:chart.line

PropDescription
dataArray of numbers.
labelsCategory labels under the plot, one per point.
baselineSecond series, drawn dashed and faint on the same axis.
areaGradient fill under the curve. Default: false.
dotsPoint markers with a stage-colored halo. Default: true.
curveOptions: smooth (monotone spline), straight. Default: smooth.
axisY ticks, grid lines and x labels. Default: true.
maxPins the axis ceiling. Default: a "nice" ceiling above the data.
widthViewBox width. Default: 360.
heightViewBox height. Default: 170.
faPersian digits on ticks and labels. Default: the enclosing card's, else the config.

mds:chart.bars

PropDescription
dataArray of numbers — or arrays, which stack into layers (bottom first).
labelsCategory labels.
secondaryFaint second series next to each bar.
horizontalHTML rows that follow the page direction — the funnel layout. Default: false.
axisY ticks, grid lines and x labels. Default: true.
maxPins the scale ceiling.
widthViewBox width. Default: 360.
heightViewBox height. Default: 170.
faPersian digits. Default: the enclosing card's, else the config.

mds:chart.donut

PropDescription
dataAssoc array — labels to values.
valueCenter readout. Default: the values' sum.
labelSmall line under the center readout.
legendTone-matched legend under the ring. Default: true.
sizeViewBox size. Default: 160.
thicknessRing thickness. Default: 22.
faPersian digits. Default: the enclosing card's, else the config.

mds:chart.gauge

PropDescription
valueThe dialed value.
maxFull-dial value. Default: 100.
labelSmall line under the readout.
decimalsDecimals in the readout. Default: 0.
faPersian digits. Default: the enclosing card's, else the config.

mds:chart.radar

PropDescription
dataAssoc array — axis labels to values.
maxThe web's outer ring value. Default: 100.
faPersian digits in labels. Default: the enclosing card's, else the config.

mds:chart.bullet

PropDescription
itemsRows: [['label' => ..., 'value' => ..., 'target' => ...], ...]target optional.
maxFull-track value. Default: 100.
unitSuffix in the readouts. Default: %.
faPersian digits. Default: the enclosing card's, else the config.

mds:chart.heatmap

PropDescription
dataArray of counts, column-major — every rows values are one column.
rowsCells per column. Default: 7.
labelsLabels spread across the top — usually months.
colorOptions: accent. Default: the ink.
unitWord in the hover callout. Default: "items" / مورد by language.
calloutThe hover-readout row (needs Alpine; tiles keep a title either way). Default: true.
faPersian digits and built-in strings. Default: the enclosing card's, else the config.

mds:chart.sparkline

A bare <svg> that stretches to any box (strokes stay 2px crisp) — size it with width/height utilities.

PropDescription
dataArray of numbers — the sparkline auto-fits its own range.
areaGradient fill under the curve. Default: false.
curveOptions: smooth, straight. Default: smooth.
widthViewBox width. Default: 120.
heightViewBox height. Default: 28.

Related