An opinionated UI kit for internal tools.
Carefully-tuned React and Vue components, two themes, and a handful of tokens. Tap anything below to see its details.
Install the CLI globally once, then run init in your project root. It scaffolds the config, drops a base stylesheet, and wires the @ alias.
npm install -g @espresso-ui/cli espresso-ui init A small, opinionated UI kit for internal tools.
Carefully-tuned React and Vue components, two themes, and a handful of tokens. Designed for clarity, not flash. Drop into any project; everything ships as copy-pasted source you can override token by token.
Install
Install the CLI globally once, then run init in your project root. It scaffolds the config, drops a base stylesheet, and wires the @ alias. No build configuration needed.
npm install -g @espresso-ui/cli espresso-ui init // after `espresso-ui init`:
import { Button } from "@/components/ui/Button";
export default function App() {
return <Button>Hello</Button>;
} Color
An 11-step neutral ramp plus five semantic accents. Tokens are mode-aware — switching the theme remaps each step automatically.
Neutrals
11 stepsSemantic
5 accentsTypography
Inter for UI, JetBrains Mono for code. Six type roles cover everything from display headlines to inline code.
Spacing
A 17-stop scale. Component padding, gaps, and stack rhythm all derive from these tokens.
Radius
Rounding stops from sharp corners to fully-pilled. Aliased into semantic radius tokens for component use.
Avatar
User avatar with image, initials, or icon fallback. Seven sizes and squircle or circle shape. Pass an icon to the status prop (React) or status slot (Vue); Avatar renders the positioned badge shell.
Preview
initials · icon · imageimport { Avatar } from "@/components/ui/Avatar";
import { Circle } from "lucide-react";
export default function App() {
return (
<Avatar
fallback="JD"
size="md"
shape="circle"
status={
<Circle className="size-(--avatar-status-icon-size) fill-green-500 text-green-500" />
}
/>
);
} <script setup>
import Avatar from "@/components/ui/Avatar.vue";
import { Circle } from "@lucide/vue";
</script>
<template>
<Avatar fallback="JD" size="md" shape="circle">
<template #status>
<Circle class="size-(--avatar-status-icon-size) fill-green-500 text-green-500" />
</template>
</Avatar>
</template> Avatar Group
Overlapping stack of circular avatars with optional overflow counter and member count label.
Preview
stack · overlap ringimport { AvatarGroup } from "@/components/ui/AvatarGroup";
export default function App() {
return (
<AvatarGroup
size="md"
max={4}
count={8}
showCount
items={[
{ fallback: "A" },
{ fallback: "B" },
{ fallback: "C" },
{ fallback: "D" },
{ fallback: "E" },
]}
/>
);
} <script setup>
import AvatarGroup from "@/components/ui/AvatarGroup.vue";
const items = [
{ fallback: "A" },
{ fallback: "B" },
{ fallback: "C" },
{ fallback: "D" },
{ fallback: "E" },
];
</script>
<template>
<AvatarGroup size="md" :max="4" :count="8" show-count :items="items" />
</template> Badge
Compact status label for counts, alerts, and metadata. Five themes and four variants at three sizes. Prefix and suffix props (React) or slots (Vue) accept icons, spinners, or any inline content.
Preview
solid · subtle · outline · ghostimport { Badge } from "@/components/ui/Badge";
import { Sparkles } from "lucide-react";
export default function App() {
return (
<Badge
theme="success"
variant="subtle"
prefix={<Sparkles aria-hidden="true" />}
>
Active
</Badge>
);
} <script setup>
import Badge from "@/components/ui/Badge.vue";
import { Sparkles } from "@lucide/vue";
</script>
<template>
<Badge theme="success" variant="subtle">
<template #prefix><Sparkles aria-hidden="true" /></template>
Active
</Badge>
</template> Label
Accessible text label for form controls. Pairs with inputs via htmlFor and supports a disabled state that dims the label and blocks pointer events.
Preview
default · disabledimport { Label } from "@/components/ui/Label";
export default function App() {
return <Label htmlFor="email">Email address</Label>;
}