Motion
JoyfulTaskCheckbox
A task checkbox with a small, restrained completion moment: one soft bloom, two sparks, then stillness.
Guidelines#
Tasks and Pipeline both use it.
Use it for completing a task, a one-way action. It checks at once and asks you to confirm the write.
What you provide
checkedandlabel, which is the accessible name.onComplete(), which returnsPromise<boolean>. Resolvefalseif the write was refused and the control settles back to idle.- Optional:
disabled(default false),disabledReason(shown as a title and anaria-description) andclassName.
Anatomy
- A
size-6box with arounded-[6px],border-2 border-foreground/65edge onbg-background. Hover givesborder-foreground bg-muted/60. - Checked:
border-emerald-500 bg-emerald-500 text-white. - Disabled:
border-foreground/45 bg-muted/30at full opacity. - The hit area extends 10px beyond the box (
after:-inset-2.5), and focus shows a 2px ring with a 2px offset.
Motion (CSS, while the write is pending)
task-complete-pop: scale 0.82 to 1.18 to 1 over 360ms,cubic-bezier(0.22, 1, 0.36, 1).- A halo in
border-emerald-400/70grows from 0.75 to 2.35 and fades out over 520ms ease-out. - Two 4px sparks:
emerald-400flies up-left (480ms) andamber-300up-right (520ms, 35ms delay). - The check mark turns in from scale 0.55 and −8° over 240ms.
prefers-reduced-motionturns all of it off. It is still a real, labelled checkbox to assistive technology.
Don't use it for settings or anything that can be unchecked (a checked box is disabled), and don't add confetti on top.
Facts#
| Group | Motion |
|---|---|
| Product | Max |
| Global | window.DigitalCrew.JoyfulTaskCheckbox |
| Source | max-agent: src/features/tasks/ui/joyful-task-checkbox.tsx |
| Import in the app | import { JoyfulTaskCheckbox } from "@/features/tasks/ui/joyful-task-checkbox"; |
API#
TypeScript declarations emitted from src/features/tasks/ui/joyful-task-checkbox.tsx.
interface JoyfulTaskCheckboxProps {
checked: boolean;
disabled?: boolean;
disabledReason?: string;
label: string;
className?: string;
/**
* Return false when the write was refused. The control then settles back to
* its idle state instead of leaving an optimistic check on screen.
*/
onComplete(): Promise<boolean>;
}
/**
* A restrained completion moment shared by Tasks and Pipeline.
*
* The check fills immediately so the click feels responsive, then the caller
* confirms whether the canonical task write succeeded. Motion is CSS-only and
* disappears under prefers-reduced-motion; the control remains a real,
* labelled checkbox to assistive technology.
*/
export declare function JoyfulTaskCheckbox({ checked, disabled, disabledReason, label, className, onComplete, }: JoyfulTaskCheckboxProps): JSX.Element;Example#
The code of the preview above: plain React.createElement against window.DigitalCrew, with realistic data.
<div id="root" class="p-6"></div>
<script>
(function () {
var h = React.createElement, DC = window.DigitalCrew, I = DC.Icons;
var TASKS = [
{ id: "t1", title: "Send Claire Dubois the Thursday call agenda", meta: "Due today · Northwind Traders" },
{ id: "t2", title: "Log the discovery call with Marcus Reinholt", meta: "Due today · Brightline Logistics" },
{ id: "t3", title: "Follow up on the Brightline pilot proposal", meta: "Due Fri · Tomás Alvarez" }
];
function App() {
var d = React.useState({}), done = d[0], setDone = d[1];
var k = React.useState(0), round = k[0], setRound = k[1];
function completer(id) {
return function () {
return new Promise(function (resolve) {
setTimeout(function () {
setDone(function (x) { var n = Object.assign({}, x); n[id] = true; return n; });
resolve(true);
}, 700);
});
};
}
React.useEffect(function () {
// Complete two tasks on their own so the burst plays without a click
// (the second lands where a still capture at ~1.5s catches it mid-bloom).
function tick(id) {
var box = document.querySelector('[data-auto="' + id + '"] button[role="checkbox"]');
if (box) box.click();
}
var a = setTimeout(function () { tick("t2"); }, 600);
var b = setTimeout(function () { tick("t3"); }, 1350);
return function () { clearTimeout(a); clearTimeout(b); };
}, [round]);
var count = Object.keys(done).length;
return h("div", { className: "mx-auto max-w-[520px] rounded-2xl border border-border/60 bg-card p-2 shadow-sm" },
h("div", { className: "flex items-center justify-between px-3 pb-1 pt-2" },
h("span", { className: "text-sm font-semibold" }, "Today"),
h("button", { type: "button", className: "text-xs text-muted-foreground hover:text-foreground",
onClick: function () { setDone({}); setRound(round + 1); } }, count + " of 3 done · reset")),
TASKS.map(function (t) {
var checked = !!done[t.id];
return h("div", { key: t.id + round, "data-auto": t.id, className: "flex items-center gap-3 rounded-xl px-3 py-2.5 hover:bg-muted/40" },
h(DC.JoyfulTaskCheckbox, { checked: checked, label: "Complete: " + t.title, onComplete: completer(t.id) }),
h("div", { className: "min-w-0 flex-1" },
h("div", { className: "truncate text-sm font-medium transition-colors " + (checked ? "text-muted-foreground line-through" : "") }, t.title),
h("div", { className: "text-xs text-muted-foreground" }, t.meta)));
}));
}
ReactDOM.createRoot(document.getElementById("root")).render(h(DC.TooltipProvider, { delayDuration: 80 }, h(App)));
})();
</script>