Skip to content

Voice Channel Row

A sidebar voice channel row with a collapsible header and the list of connected members.

A sidebar voice channel row with a collapsible header and the list of connected members. Compose it from the exported registry parts so local content and behaviour stay in the application while the visual contract stays token-driven.

Root class newt-voice-channel
import { Avatar } from "@/registry/bases/newt/ui/avatar"
import {
  VoiceChannel,
  VoiceChannelHeader,
  VoiceChannelIcon,
  VoiceChannelMember,
  VoiceChannelMemberIcons,
  VoiceChannelMembers,
} from "@/registry/bases/newt/ui/voice-channel"

export default function VoiceChannelDemo() {
  return (
    <VoiceChannel className="w-60">
      <VoiceChannelHeader>
        <VoiceChannelIcon>🔊</VoiceChannelIcon>
        Channel name
      </VoiceChannelHeader>
      <VoiceChannelMembers>
        <VoiceChannelMember>
          <Avatar size="sm">A</Avatar>
          username
          <VoiceChannelMemberIcons>🎙️</VoiceChannelMemberIcons>
        </VoiceChannelMember>
      </VoiceChannelMembers>
    </VoiceChannel>
  )
}
<script setup lang="ts">
import { Avatar } from "~~/registry/bases/newt/ui/avatar"
import {
  VoiceChannel,
  VoiceChannelHeader,
  VoiceChannelIcon,
  VoiceChannelMember,
  VoiceChannelMemberIcons,
  VoiceChannelMembers,
} from "~~/registry/bases/newt/ui/voice-channel"
</script>

<template>
  <VoiceChannel class="w-60">
    <VoiceChannelHeader>
      <VoiceChannelIcon>🔊</VoiceChannelIcon>
      Channel name
    </VoiceChannelHeader>
    <VoiceChannelMembers>
      <VoiceChannelMember>
        <Avatar size="sm">A</Avatar>
        username
        <VoiceChannelMemberIcons>🎙️</VoiceChannelMemberIcons>
      </VoiceChannelMember>
    </VoiceChannelMembers>
  </VoiceChannel>
</template>

Installation

pnpm dlx newtui@latest add voice-channel
npx newtui@latest add voice-channel
yarn dlx newtui@latest add voice-channel
bunx newtui@latest add voice-channel

Add the components it builds on

Copy the source into your project

import * as React from "react"

import { cn } from "@/lib/utils"

const VoiceChannel = React.forwardRef<
  HTMLDivElement,
  React.ComponentProps<"div">
>(({ className, ...props }, ref) => (
  <div
    ref={ref}
    className={cn("flex flex-col gap-1 rounded-sm px-2 py-1.5", className)}
    {...props}
  />
))
VoiceChannel.displayName = "VoiceChannel"

const VoiceChannelHeader = React.forwardRef<
  HTMLDivElement,
  React.ComponentProps<"div">
>(({ className, ...props }, ref) => (
  <div
    ref={ref}
    className={cn(
      "flex cursor-pointer items-center gap-1.5 text-[15px] font-medium text-newt-text-muted transition-colors duration-fast ease-newt hover:text-newt-text-secondary",
      className
    )}
    {...props}
  />
))
VoiceChannelHeader.displayName = "VoiceChannelHeader"

const VoiceChannelIcon = React.forwardRef<
  HTMLSpanElement,
  React.ComponentProps<"span">
>(({ className, ...props }, ref) => (
  <span
    ref={ref}
    aria-hidden="true"
    className={cn("shrink-0 opacity-80", className)}
    {...props}
  />
))
VoiceChannelIcon.displayName = "VoiceChannelIcon"

const VoiceChannelMembers = React.forwardRef<
  HTMLDivElement,
  React.ComponentProps<"div">
>(({ className, ...props }, ref) => (
  <div
    ref={ref}
    className={cn("flex flex-col gap-0.5 ps-6", className)}
    {...props}
  />
))
VoiceChannelMembers.displayName = "VoiceChannelMembers"

const VoiceChannelMember = React.forwardRef<
  HTMLDivElement,
  React.ComponentProps<"div">
>(({ className, ...props }, ref) => (
  <div
    ref={ref}
    className={cn(
      "flex cursor-pointer items-center gap-2 rounded-sm px-1.5 py-[3px] text-sm text-newt-text-secondary transition-colors duration-fast ease-newt hover:bg-newt-bg-hover",
      className
    )}
    {...props}
  />
))
VoiceChannelMember.displayName = "VoiceChannelMember"

export interface VoiceChannelMemberIconsProps extends React.ComponentProps<"div"> {
  /** Renders the icons in the dnd color to signal a muted / deafened member. */
  muted?: boolean
}

const VoiceChannelMemberIcons = React.forwardRef<
  HTMLDivElement,
  VoiceChannelMemberIconsProps
>(({ className, muted = false, ...props }, ref) => (
  <div
    ref={ref}
    data-muted={muted ? "" : undefined}
    className={cn(
      "ms-auto flex items-center gap-1 text-xs text-newt-text-muted",
      muted && "text-newt-dnd",
      className
    )}
    {...props}
  />
))
VoiceChannelMemberIcons.displayName = "VoiceChannelMemberIcons"

export {
  VoiceChannel,
  VoiceChannelHeader,
  VoiceChannelIcon,
  VoiceChannelMembers,
  VoiceChannelMember,
  VoiceChannelMemberIcons,
}
<script setup lang="ts">
import type { HTMLAttributes } from "vue"

import { cn } from "@/lib/utils"

const props = defineProps<{ class?: HTMLAttributes["class"] }>()
</script>

<template>
  <div :class="cn('flex flex-col gap-1 rounded-sm px-2 py-1.5', props.class)">
    <slot />
  </div>
</template>
<script setup lang="ts">
import type { HTMLAttributes } from "vue"

import { cn } from "@/lib/utils"

const props = defineProps<{ class?: HTMLAttributes["class"] }>()
</script>

<template>
  <div
    :class="
      cn(
        'flex cursor-pointer items-center gap-1.5 text-[15px] font-medium text-newt-text-muted transition-colors duration-fast ease-newt hover:text-newt-text-secondary',
        props.class
      )
    "
  >
    <slot />
  </div>
</template>
<script setup lang="ts">
import type { HTMLAttributes } from "vue"

import { cn } from "@/lib/utils"

const props = defineProps<{ class?: HTMLAttributes["class"] }>()
</script>

<template>
  <span aria-hidden="true" :class="cn('shrink-0 opacity-80', props.class)">
    <slot />
  </span>
</template>
<script setup lang="ts">
import type { HTMLAttributes } from "vue"

import { cn } from "@/lib/utils"

const props = defineProps<{ class?: HTMLAttributes["class"] }>()
</script>

<template>
  <div :class="cn('flex flex-col gap-0.5 ps-6', props.class)">
    <slot />
  </div>
</template>
<script setup lang="ts">
import type { HTMLAttributes } from "vue"

import { cn } from "@/lib/utils"

const props = defineProps<{ class?: HTMLAttributes["class"] }>()
</script>

<template>
  <div
    :class="
      cn(
        'flex cursor-pointer items-center gap-2 rounded-sm px-1.5 py-[3px] text-sm text-newt-text-secondary transition-colors duration-fast ease-newt hover:bg-newt-bg-hover',
        props.class
      )
    "
  >
    <slot />
  </div>
</template>
<script setup lang="ts">
import type { HTMLAttributes } from "vue"

import { cn } from "@/lib/utils"

const props = defineProps<{
  class?: HTMLAttributes["class"]
  /** Renders the icons in the dnd color to signal a muted / deafened member. */
  muted?: boolean
}>()
</script>

<template>
  <div
    :data-muted="props.muted ? '' : undefined"
    :class="
      cn(
        'ms-auto flex items-center gap-1 text-xs text-newt-text-muted',
        props.muted && 'text-newt-dnd',
        props.class
      )
    "
  >
    <slot />
  </div>
</template>
export { default as VoiceChannel } from "./VoiceChannel.vue"
export { default as VoiceChannelHeader } from "./VoiceChannelHeader.vue"
export { default as VoiceChannelIcon } from "./VoiceChannelIcon.vue"
export { default as VoiceChannelMembers } from "./VoiceChannelMembers.vue"
export { default as VoiceChannelMember } from "./VoiceChannelMember.vue"
export { default as VoiceChannelMemberIcons } from "./VoiceChannelMemberIcons.vue"

Update the import paths

The files import @/lib/utils and sibling components through the aliases in your components.json. Point them at wherever those live in your project.

Usage

import { VoiceChannel } from "@/components/ui/voice-channel"

export function Example() {
  return <VoiceChannel />
}
<script setup lang="ts">
import { VoiceChannel, VoiceChannelHeader, VoiceChannelIcon, VoiceChannelMembers, VoiceChannelMember, VoiceChannelMemberIcons } from "@/components/ui/voice-channel"
</script>

<template>
  <VoiceChannel />
</template>

API reference

VoiceChannelMemberIcons

PropType
muted?boolean | undefined

Renders the icons in the dnd color to signal a muted / deafened member.

Typeboolean | undefined

The registry exports VoiceChannel, VoiceChannelHeader, VoiceChannelIcon, VoiceChannelMembers, VoiceChannelMember, VoiceChannelMemberIcons. Parts without a table accept the native attributes for the element they render; React uses className, while Vue uses class and fallthrough attributes.

Accessibility

Preserve the native semantics of the rendered elements and pass through ARIA attributes when the surrounding context needs a label or description. Any interactive child needs a visible focus indicator, and meaning must not rely on colour alone.

Tokens

Voice Channel Row reads its colours, radii and motion from the --newt-* tokens installed with the library. Override a token and every component that reads it follows, including this one — see the token layer. In the plain HTML and CSS distribution the same component is newt-voice-channel.