Actions
Button
The action control for both products: filled with primary by default, in six variants and six sizes (Max's components/ui/button.tsx).
Guidelines#
Variants
| variant | fill / text | use |
|---|---|---|
default |
primary / primary-foreground; hover 90% |
The one main action per view. Ink in Crew OS, orange in Max. |
secondary |
secondary / secondary-foreground; hover 80% |
Quiet filled actions beside a default one ("Save draft"). |
outline |
background + border + shadow-xs; dark: input at 30% |
Cancel, toolbar actions, icon buttons on busy rows. |
ghost |
none; hover accent |
Low-emphasis actions in dense UI: table rows, card footers. |
destructive |
destructive / white; dark themes at 60% |
Irreversible actions only, behind a confirm. |
link |
primary text, underline on hover |
Inline actions inside text. |
Sizes: sm 32px, default 36px, lg 40px; icon-sm, icon, icon-lg are squares of the same heights. icon-sm (32px) is the smallest target Max allows; use icon or larger on touch. Radius radius-md, text 14px medium.
What you provide: a verb-first label in sentence case ("Launch campaign", "Review drafts"); 16px Lucide icons as children (the button sizes and spaces them); aria-label on every icon-only button; disabled with a Loader2 spinner and a "…" label while working ("Writing…"). Keep the changing label in its own element so browser translation doesn't break it.
Weight follows cost: only actions that spend money or send something get the filled default; AI actions carry a Sparkles icon and a spinner while the model writes, then drop back to outline once their content is on screen.
Focus: ring border plus a 3px halo at 50% (focus-visible only). The shared @digitalcrew/ui Button draws the halo at full strength and adds the Glass finish; any Button takes it with data-crew-button-finish="glass" and className="relative isolate".
Don't put two default buttons side by side, use destructive for anything recoverable, or recolour a button with an agent or status colour.
Facts#
| Group | Actions |
|---|---|
| Product | Max |
| Global | window.DigitalCrew.Button |
| Source | max-agent: src/components/ui/button.tsx |
| Import in the app | import { Button } from "@/components/ui/button"; |
API#
TypeScript declarations emitted from src/components/ui/button.tsx (the module also exports its other parts).
declare const buttonVariants: (props?: {
variant?: "default" | "destructive" | "ghost" | "link" | "outline" | "secondary";
size?: "default" | "icon" | "icon-lg" | "icon-sm" | "lg" | "sm";
} & ClassProp) => string;
declare function Button({ className, variant, size, asChild, ...props }: React.ComponentProps<"button"> & VariantProps<typeof buttonVariants> & {
asChild?: boolean;
}): any;
export { Button, buttonVariants };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;
function Row(label, kids) {
return h("div", { className: "flex flex-col gap-2" },
h("span", { className: "text-xs text-muted-foreground" }, label),
h("div", { className: "flex flex-wrap items-center gap-3" }, kids));
}
function App() {
return h("div", { className: "flex flex-col gap-5" },
Row("Variants", [
h(DC.Button, { key: 1 }, h(I.Sparkles), "Write my campaign"),
h(DC.Button, { key: 2, variant: "secondary" }, "Save draft"),
h(DC.Button, { key: 3, variant: "outline" }, "Cancel"),
h(DC.Button, { key: 4, variant: "ghost" }, "Skip"),
h(DC.Button, { key: 5, variant: "destructive" }, "Delete campaign"),
h(DC.Button, { key: 6, variant: "link" }, "View report")
]),
Row("Sizes and icons", [
h(DC.Button, { key: 1, size: "sm", variant: "outline" }, h(I.Plus), "New list"),
h(DC.Button, { key: 2 }, "Continue", h(I.ArrowRight)),
h(DC.Button, { key: 3, size: "lg" }, "Launch campaign"),
h(DC.Button, { key: 4, size: "icon", variant: "outline", "aria-label": "Add prospect" }, h(I.Plus)),
h(DC.Button, { key: 5, size: "icon-sm", variant: "ghost", "aria-label": "More actions" }, h(I.MoreHorizontal)),
h(DC.Button, { key: 6, disabled: true }, h(I.Loader2, { className: "animate-spin" }), "Writing…")
]),
Row("Glass finish (Appearance setting)", [
h(DC.Button, { key: 1, "data-crew-button-finish": "glass", className: "relative isolate" }, "Review drafts"),
h(DC.Button, { key: 2, "data-crew-button-finish": "glass", className: "relative isolate", size: "lg" }, "Get started")
])
);
}
ReactDOM.createRoot(document.getElementById("root")).render(h(DC.TooltipProvider, { delayDuration: 80 }, h(App)));
})();
</script>