newt/uiv0.3.0
Discord-inspired UI, copy-pasted into your project.
A component library, a design token system and an agent guide for building Discord-inspired interfaces — bots, dashboards and docs that look like they belong in the client. Every component ships as plain HTML and CSS, as a React file, and as a Vue single file component, all reading one set of tokens.
- components
- 71
- runtime deps
- 0
- license
- Apache-2.0
- install
- npx newtui
- tokens
- CSS vars
- frameworks
- React · Vue · HTML
Install
One CLI for both frameworks. It reads your project during init, then writes component source into it — there is no package between you and the markup.
pnpm dlx newtui@latest initnpx newtui@latest inityarn dlx newtui@latest initbunx newtui@latest initThree of them, running
The same examples every component page carries, rendered from the registry source in the framework the header has chosen.
newt-message-groupimport { Avatar } from "@/registry/bases/newt/ui/avatar"
import {
MessageGroup,
MessageGroupAuthor,
MessageGroupAvatar,
MessageGroupBody,
MessageGroupBotTag,
MessageGroupContent,
MessageGroupEphemeralAction,
MessageGroupEphemeralNotice,
MessageGroupHeader,
MessageGroupReactions,
MessageGroupReply,
MessageGroupTime,
} from "@/registry/bases/newt/ui/message-group"
import { Reaction } from "@/registry/bases/newt/ui/reaction"
import {
ReplyPreview,
ReplyPreviewAuthor,
ReplyPreviewText,
} from "@/registry/bases/newt/ui/reply-preview"
import {
StatusDot,
StatusIndicator,
} from "@/registry/bases/newt/ui/status-indicator"
export default function MessageGroupDemo() {
return (
<div className="flex w-full max-w-md flex-col gap-2">
<MessageGroup withReply>
<MessageGroupReply>
<ReplyPreview>
<ReplyPreviewAuthor>@someone</ReplyPreviewAuthor>
<ReplyPreviewText>original message text</ReplyPreviewText>
</ReplyPreview>
</MessageGroupReply>
<MessageGroupAvatar>
<StatusIndicator>
<Avatar>U</Avatar>
<StatusDot status="online" />
</StatusIndicator>
</MessageGroupAvatar>
<MessageGroupBody>
<MessageGroupHeader>
<MessageGroupAuthor>username</MessageGroupAuthor>
<MessageGroupBotTag verified />
<MessageGroupTime dateTime="2026-09-04T14:23:00.000Z">
Today at 14:23
</MessageGroupTime>
</MessageGroupHeader>
<MessageGroupContent>Message content goes here.</MessageGroupContent>
<MessageGroupReactions>
<Reaction emoji="👍" count={1} />
</MessageGroupReactions>
</MessageGroupBody>
</MessageGroup>
<MessageGroup ephemeral>
<MessageGroupAvatar>
<Avatar>B</Avatar>
</MessageGroupAvatar>
<MessageGroupBody>
<MessageGroupHeader>
<MessageGroupAuthor>botname</MessageGroupAuthor>
<MessageGroupBotTag>APP</MessageGroupBotTag>
</MessageGroupHeader>
<MessageGroupContent>Command ran successfully.</MessageGroupContent>
<MessageGroupEphemeralNotice>
Only you can see this •
<MessageGroupEphemeralAction>
Dismiss message
</MessageGroupEphemeralAction>
</MessageGroupEphemeralNotice>
</MessageGroupBody>
</MessageGroup>
</div>
)
}<script setup lang="ts">
import { Avatar } from "~~/registry/bases/newt/ui/avatar"
import {
MessageGroup,
MessageGroupAuthor,
MessageGroupAvatar,
MessageGroupBody,
MessageGroupBotTag,
MessageGroupContent,
MessageGroupEphemeralAction,
MessageGroupEphemeralNotice,
MessageGroupHeader,
MessageGroupReactions,
MessageGroupReply,
MessageGroupTime,
} from "~~/registry/bases/newt/ui/message-group"
import { Reaction } from "~~/registry/bases/newt/ui/reaction"
import {
ReplyPreview,
ReplyPreviewAuthor,
ReplyPreviewText,
} from "~~/registry/bases/newt/ui/reply-preview"
import {
StatusDot,
StatusIndicator,
} from "~~/registry/bases/newt/ui/status-indicator"
</script>
<template>
<div class="flex w-full max-w-md flex-col gap-2">
<MessageGroup with-reply>
<MessageGroupReply>
<ReplyPreview>
<ReplyPreviewAuthor>@someone</ReplyPreviewAuthor>
<ReplyPreviewText>original message text</ReplyPreviewText>
</ReplyPreview>
</MessageGroupReply>
<MessageGroupAvatar>
<StatusIndicator>
<Avatar>U</Avatar>
<StatusDot status="online" />
</StatusIndicator>
</MessageGroupAvatar>
<MessageGroupBody>
<MessageGroupHeader>
<MessageGroupAuthor>username</MessageGroupAuthor>
<MessageGroupBotTag verified />
<MessageGroupTime datetime="2026-09-04T14:23:00.000Z">
Today at 14:23
</MessageGroupTime>
</MessageGroupHeader>
<MessageGroupContent>Message content goes here.</MessageGroupContent>
<MessageGroupReactions>
<Reaction emoji="👍" :count="1" />
</MessageGroupReactions>
</MessageGroupBody>
</MessageGroup>
<MessageGroup ephemeral>
<MessageGroupAvatar>
<Avatar>B</Avatar>
</MessageGroupAvatar>
<MessageGroupBody>
<MessageGroupHeader>
<MessageGroupAuthor>botname</MessageGroupAuthor>
<MessageGroupBotTag>APP</MessageGroupBotTag>
</MessageGroupHeader>
<MessageGroupContent>Command ran successfully.</MessageGroupContent>
<MessageGroupEphemeralNotice>
Only you can see this •
<MessageGroupEphemeralAction>
Dismiss message
</MessageGroupEphemeralAction>
</MessageGroupEphemeralNotice>
</MessageGroupBody>
</MessageGroup>
</div>
</template>newt-memberimport { Avatar } from "@/registry/bases/newt/ui/avatar"
import {
MemberList,
MemberListAppTag,
MemberListHeading,
MemberListInfo,
MemberListItem,
MemberListName,
MemberListNameRow,
MemberListRole,
MemberListSection,
} from "@/registry/bases/newt/ui/member-list"
import { RoleTag } from "@/registry/bases/newt/ui/role-tag"
import {
StatusDot,
StatusIndicator,
} from "@/registry/bases/newt/ui/status-indicator"
export default function MemberListDemo() {
return (
<MemberList className="w-60">
<MemberListSection>
<MemberListHeading>Moderators — 1</MemberListHeading>
<MemberListItem>
<StatusIndicator>
<Avatar size="sm">A</Avatar>
<StatusDot status="online" />
</StatusIndicator>
<MemberListInfo>
<MemberListNameRow>
<MemberListName color="#f0b232">username</MemberListName>
<MemberListAppTag verified />
</MemberListNameRow>
<MemberListRole>Role</MemberListRole>
</MemberListInfo>
<RoleTag className="ml-auto">TAG</RoleTag>
</MemberListItem>
</MemberListSection>
<MemberListSection offline>
<MemberListHeading>Offline — 1</MemberListHeading>
<MemberListItem>
<StatusIndicator>
<Avatar size="sm">B</Avatar>
<StatusDot status="offline" />
</StatusIndicator>
<MemberListInfo>
<MemberListNameRow>
<MemberListName>someone</MemberListName>
</MemberListNameRow>
</MemberListInfo>
</MemberListItem>
</MemberListSection>
</MemberList>
)
}<script setup lang="ts">
import { Avatar } from "~~/registry/bases/newt/ui/avatar"
import {
MemberList,
MemberListAppTag,
MemberListHeading,
MemberListInfo,
MemberListItem,
MemberListName,
MemberListNameRow,
MemberListRole,
MemberListSection,
} from "~~/registry/bases/newt/ui/member-list"
import { RoleTag } from "~~/registry/bases/newt/ui/role-tag"
import {
StatusDot,
StatusIndicator,
} from "~~/registry/bases/newt/ui/status-indicator"
</script>
<template>
<MemberList class="w-60">
<MemberListSection>
<MemberListHeading>Moderators — 1</MemberListHeading>
<MemberListItem>
<StatusIndicator>
<Avatar size="sm">A</Avatar>
<StatusDot status="online" />
</StatusIndicator>
<MemberListInfo>
<MemberListNameRow>
<MemberListName color="#f0b232">username</MemberListName>
<MemberListAppTag verified />
</MemberListNameRow>
<MemberListRole>Role</MemberListRole>
</MemberListInfo>
<RoleTag class="ml-auto">TAG</RoleTag>
</MemberListItem>
</MemberListSection>
<MemberListSection offline>
<MemberListHeading>Offline — 1</MemberListHeading>
<MemberListItem>
<StatusIndicator>
<Avatar size="sm">B</Avatar>
<StatusDot status="offline" />
</StatusIndicator>
<MemberListInfo>
<MemberListNameRow>
<MemberListName>someone</MemberListName>
</MemberListNameRow>
</MemberListInfo>
</MemberListItem>
</MemberListSection>
</MemberList>
</template>newt-select-menu"use client"
import * as React from "react"
import { SelectMenu } from "@/registry/bases/newt/ui/select-menu"
/*
* Drawn here rather than pulled from an icon package: the registry ships no
* runtime dependency, and a device picker needs exactly two glyphs.
*/
const Monitor = () => (
<svg viewBox="0 0 24 24" className="h-full w-full" aria-hidden="true">
<path
fill="currentColor"
d="M4 5h16a1 1 0 0 1 1 1v9a1 1 0 0 1-1 1h-6v2h3a1 1 0 1 1 0 2H7a1 1 0 1 1 0-2h3v-2H4a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1Zm1 2v7h14V7H5Z"
/>
</svg>
)
const Headset = () => (
<svg viewBox="0 0 24 24" className="h-full w-full" aria-hidden="true">
<path
fill="currentColor"
d="M12 3a8 8 0 0 0-8 8v6a3 3 0 0 0 3 3h1a1 1 0 0 0 1-1v-6a1 1 0 0 0-1-1H6v-1a6 6 0 1 1 12 0v1h-2a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h1a3 3 0 0 0 3-3v-6a8 8 0 0 0-8-8Z"
/>
</svg>
)
const OPTIONS = [
{
value: "default",
label: "System Default",
description: "Speakers (Built-in Output)",
note: "(Speakers)",
icon: <Monitor />,
},
{
value: "speakers",
label: "Speakers",
description: "Built-in Output",
icon: <Monitor />,
},
{
value: "headset",
label: "Headset",
description: "USB Audio Device",
icon: <Headset />,
},
{
value: "bluetooth",
label: "Bluetooth Headset",
description: "Not connected",
icon: <Headset />,
disabled: true,
},
]
export default function SelectMenuDemo() {
const [value, setValue] = React.useState<string>("default")
return (
<SelectMenu
options={OPTIONS}
value={value}
onValueChange={setValue}
label="Output device"
/>
)
}<script setup lang="ts">
import { h, markRaw, ref } from "vue"
import {
SelectMenu,
type SelectMenuOption,
} from "~~/registry/bases/newt/ui/select-menu"
/*
* Drawn here rather than pulled from an icon package: the registry ships no
* runtime dependency, and a device picker needs exactly two glyphs.
*/
const Monitor = markRaw({
render: () =>
h(
"svg",
{ viewBox: "0 0 24 24", class: "h-full w-full", "aria-hidden": "true" },
[
h("path", {
fill: "currentColor",
d: "M4 5h16a1 1 0 0 1 1 1v9a1 1 0 0 1-1 1h-6v2h3a1 1 0 1 1 0 2H7a1 1 0 1 1 0-2h3v-2H4a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1Zm1 2v7h14V7H5Z",
}),
]
),
})
const Headset = markRaw({
render: () =>
h(
"svg",
{ viewBox: "0 0 24 24", class: "h-full w-full", "aria-hidden": "true" },
[
h("path", {
fill: "currentColor",
d: "M12 3a8 8 0 0 0-8 8v6a3 3 0 0 0 3 3h1a1 1 0 0 0 1-1v-6a1 1 0 0 0-1-1H6v-1a6 6 0 1 1 12 0v1h-2a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h1a3 3 0 0 0 3-3v-6a8 8 0 0 0-8-8Z",
}),
]
),
})
const OPTIONS: SelectMenuOption[] = [
{
value: "default",
label: "System Default",
description: "Speakers (Built-in Output)",
note: "(Speakers)",
icon: Monitor,
},
{
value: "speakers",
label: "Speakers",
description: "Built-in Output",
icon: Monitor,
},
{
value: "headset",
label: "Headset",
description: "USB Audio Device",
icon: Headset,
},
{
value: "bluetooth",
label: "Bluetooth Headset",
description: "Not connected",
icon: Headset,
disabled: true,
},
]
const value = ref("default")
</script>
<template>
<SelectMenu v-model="value" :options="OPTIONS" label="Output device" />
</template>What it is
One token system, three targets
You own the code
No motion by default
Accessible by construction
Where to go next
Start with the installation guide, browse the components, or read how to drive the registry from an agent in Using with AI. Issues and the registry source are on GitHub.