August 2026 — Components rework
Twelve new components, an opt-in light palette, and a pass over every existing component against the original specs.
The registry grew from 43 components to 55, gained a light surface set, and every component that already existed was reworked against its original spec.
New components
InlineCode, ActionRow, MessageList, MessageComposer, Chat, Invite,
ChannelHeader, ChannelWelcome, ChannelInfo, SlashCommandSuggestions,
V2Container and AppLauncher, plus a ReactionGroup part on Reaction.
"use client"
import * as React from "react"
import { Avatar } from "@/registry/bases/newt/ui/avatar"
import { Chat, ChatBody, ChatFooter } from "@/registry/bases/newt/ui/chat"
import { MessageComposer } from "@/registry/bases/newt/ui/message-composer"
import {
MessageGroup,
MessageGroupAuthor,
MessageGroupAvatar,
MessageGroupBody,
MessageGroupContent,
MessageGroupHeader,
MessageGroupTime,
} from "@/registry/bases/newt/ui/message-group"
import { MessageList } from "@/registry/bases/newt/ui/message-list"
export default function ChatDemo() {
const [value, setValue] = React.useState("")
return (
<Chat aria-label="#general channel chat" className="h-80 w-full max-w-md">
<ChatBody focusable viewportLabel="#general channel messages">
<MessageList label="Messages in #general">
<MessageGroup>
<MessageGroupAvatar>
<Avatar>U</Avatar>
</MessageGroupAvatar>
<MessageGroupBody>
<MessageGroupHeader>
<MessageGroupAuthor>username</MessageGroupAuthor>
<MessageGroupTime dateTime="2026-09-04T14:23:00.000Z">
Today at 14:23
</MessageGroupTime>
</MessageGroupHeader>
<MessageGroupContent>Welcome to the channel.</MessageGroupContent>
</MessageGroupBody>
</MessageGroup>
</MessageList>
</ChatBody>
<ChatFooter>
<MessageComposer
channelName="general"
value={value}
onValueChange={setValue}
onSubmit={() => setValue("")}
/>
</ChatFooter>
</Chat>
)
}<script setup lang="ts">
import { ref } from "vue"
import { Avatar } from "~~/registry/bases/newt/ui/avatar"
import { Chat, ChatBody, ChatFooter } from "~~/registry/bases/newt/ui/chat"
import { MessageComposer } from "~~/registry/bases/newt/ui/message-composer"
import {
MessageGroup,
MessageGroupAuthor,
MessageGroupAvatar,
MessageGroupBody,
MessageGroupContent,
MessageGroupHeader,
MessageGroupTime,
} from "~~/registry/bases/newt/ui/message-group"
import { MessageList } from "~~/registry/bases/newt/ui/message-list"
const value = ref("")
</script>
<template>
<Chat aria-label="#general channel chat" class="h-80 w-full max-w-md">
<ChatBody focusable viewport-label="#general channel messages">
<MessageList label="Messages in #general">
<MessageGroup>
<MessageGroupAvatar>
<Avatar>U</Avatar>
</MessageGroupAvatar>
<MessageGroupBody>
<MessageGroupHeader>
<MessageGroupAuthor>username</MessageGroupAuthor>
<MessageGroupTime datetime="2026-09-04T14:23:00.000Z">
Today at 14:23
</MessageGroupTime>
</MessageGroupHeader>
<MessageGroupContent>Welcome to the channel.</MessageGroupContent>
</MessageGroupBody>
</MessageGroup>
</MessageList>
</ChatBody>
<ChatFooter>
<MessageComposer
v-model="value"
channel-name="general"
@submit="value = ''"
/>
</ChatFooter>
</Chat>
</template>An opt-in light palette
.newt-light and [data-newt-theme="light"] override the surface, text, status
and shadow tokens for light backgrounds. Nothing changes for anyone who never
asks for it: the dark values stay the default in :root.
Four tokens are new — --newt-bg-input-elevated, --newt-mention-text,
--newt-mention-role and --newt-embed-max-width — and the hex values that
had been duplicated across the mention, badge, timeline, reaction and
slash-command components now read those tokens instead.
Reworked components
Mentionrenders a real button with focus styles, and gains an optional avatar, anappvariant and acolorprop for role mentions.Buttongainsemojiand the link-out glyph, on by default forvariant="link".Embedgains an accent colour, an author row, a footer icon, a middle-dot separator and a<time>timestamp.MemberListgains role sections with headings, an offline state, coloured names and the APP tag with its verified check.ReplyPreviewgains an avatar, a “used” action and a slash-command chip; the spine is driven by custom properties.MessageGroupgains a reply grid layout, an ephemeral state with its notice, the verified bot tag, and renders its timestamp as<time>.Dividergainsspacingandline;ScrollAreagainsfocusableandviewportLabel.SlashCommandnow takes the full invocation: the subcommand path plus typed options, including the focused one.SelectMenuis a complete combobox: a trigger, a keyboard-driven listbox and a portalled panel that flips when it runs out of space.
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>