Invite
The server invite card a link unfurls into inside a message.
The server invite card a link unfurls into inside a message. Compose it from the exported registry parts so local content and behaviour stay in the application while the visual contract stays token-driven.
import { Button } from "@/registry/bases/newt/ui/button"
import {
Invite,
InviteBody,
InviteCount,
InviteCounts,
InviteEyebrow,
InviteIcon,
InviteInfo,
InviteName,
} from "@/registry/bases/newt/ui/invite"
/* Inline so the demo never reaches out to the network for a placeholder. */
const SERVER_ICON =
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48'%3E%3Crect width='48' height='48' rx='12' fill='%235865f2'/%3E%3C/svg%3E"
export default function InviteDemo() {
return (
<Invite>
<InviteEyebrow>You've been invited to join a server</InviteEyebrow>
<InviteBody>
<InviteIcon src={SERVER_ICON} />
<InviteInfo>
<InviteName href="#">Newt Community</InviteName>
<InviteCounts>
<InviteCount online>1,024 Online</InviteCount>
<InviteCount>8,192 Members</InviteCount>
</InviteCounts>
</InviteInfo>
<Button variant="success">Join</Button>
</InviteBody>
</Invite>
)
}<script setup lang="ts">
import { Button } from "~~/registry/bases/newt/ui/button"
import {
Invite,
InviteBody,
InviteCount,
InviteCounts,
InviteEyebrow,
InviteIcon,
InviteInfo,
InviteName,
} from "~~/registry/bases/newt/ui/invite"
/* Inline so the demo never reaches out to the network for a placeholder. */
const SERVER_ICON =
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48'%3E%3Crect width='48' height='48' rx='12' fill='%235865f2'/%3E%3C/svg%3E"
</script>
<template>
<Invite>
<InviteEyebrow>You've been invited to join a server</InviteEyebrow>
<InviteBody>
<InviteIcon :src="SERVER_ICON" />
<InviteInfo>
<InviteName href="#">Newt Community</InviteName>
<InviteCounts>
<InviteCount online>1,024 Online</InviteCount>
<InviteCount>8,192 Members</InviteCount>
</InviteCounts>
</InviteInfo>
<Button variant="success">Join</Button>
</InviteBody>
</Invite>
</template>Installation
pnpm dlx newtui@latest add invitenpx newtui@latest add inviteyarn dlx newtui@latest add invitebunx newtui@latest add inviteAdd the components it builds on
Copy the source into your project
import * as React from "react"
import { cn } from "@/lib/utils"
/* The server-invite card a link unfurls into inside a message. */
const Invite = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
"max-w-[432px] rounded-md bg-newt-bg-surface px-4 pb-4 pt-2 text-newt-text-primary",
className
)}
{...props}
/>
))
Invite.displayName = "Invite"
const InviteEyebrow = React.forwardRef<
HTMLSpanElement,
React.HTMLAttributes<HTMLSpanElement>
>(({ className, ...props }, ref) => (
<span
ref={ref}
className={cn(
"text-[12px] font-extrabold uppercase tracking-[0.02em] text-newt-text-muted",
className
)}
{...props}
/>
))
InviteEyebrow.displayName = "InviteEyebrow"
const InviteBody = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("mt-2 flex items-center gap-4", className)}
{...props}
/>
))
InviteBody.displayName = "InviteBody"
const InviteIcon = React.forwardRef<
HTMLImageElement,
React.ComponentProps<"img">
>(({ className, alt = "", ...props }, ref) => (
<img
ref={ref}
alt={alt}
aria-hidden={alt === "" ? true : undefined}
className={cn("h-12 w-12 shrink-0 rounded-md object-cover", className)}
{...props}
/>
))
InviteIcon.displayName = "InviteIcon"
const InviteInfo = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("min-w-0 flex-1", className)} {...props} />
))
InviteInfo.displayName = "InviteInfo"
const InviteName = React.forwardRef<
HTMLAnchorElement,
React.ComponentProps<"a">
>(({ className, ...props }, ref) => (
<a
ref={ref}
className={cn(
"block truncate font-bold text-newt-text-primary no-underline hover:underline",
className
)}
{...props}
/>
))
InviteName.displayName = "InviteName"
const InviteCounts = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
"mt-0.5 flex items-center gap-3 text-sm text-newt-text-muted",
className
)}
{...props}
/>
))
InviteCounts.displayName = "InviteCounts"
export interface InviteCountProps extends React.HTMLAttributes<HTMLSpanElement> {
/** Green dot for the online count, muted for the member total. */
online?: boolean
}
const InviteCount = React.forwardRef<HTMLSpanElement, InviteCountProps>(
({ className, online = false, children, ...props }, ref) => (
<span
ref={ref}
className={cn("inline-flex items-center gap-1.5", className)}
{...props}
>
<span
aria-hidden="true"
className={cn(
"h-2 w-2 shrink-0 rounded-full",
online ? "bg-newt-online" : "bg-newt-offline"
)}
/>
{children}
</span>
)
)
InviteCount.displayName = "InviteCount"
export {
Invite,
InviteEyebrow,
InviteBody,
InviteIcon,
InviteInfo,
InviteName,
InviteCounts,
InviteCount,
}<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(
'max-w-[432px] rounded-md bg-newt-bg-surface px-4 pb-4 pt-2 text-newt-text-primary',
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
:class="
cn(
'text-[12px] font-extrabold uppercase tracking-[0.02em] text-newt-text-muted',
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('mt-2 flex items-center gap-4', props.class)">
<slot />
</div>
</template><script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"
const props = withDefaults(
defineProps<{ class?: HTMLAttributes["class"]; src: string; alt?: string }>(),
{ alt: "" }
)
</script>
<template>
<img
:src="props.src"
:alt="props.alt"
:aria-hidden="props.alt === '' ? true : undefined"
:class="cn('h-12 w-12 shrink-0 rounded-md object-cover', props.class)"
/>
</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('min-w-0 flex-1', 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>
<a
:class="
cn(
'block truncate font-bold text-newt-text-primary no-underline hover:underline',
props.class
)
"
>
<slot />
</a>
</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(
'mt-0.5 flex items-center gap-3 text-sm text-newt-text-muted',
props.class
)
"
>
<slot />
</div>
</template><script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"
const props = withDefaults(
defineProps<{
class?: HTMLAttributes["class"]
/** Green dot for the online count, muted for the member total. */
online?: boolean
}>(),
{ online: false }
)
</script>
<template>
<span :class="cn('inline-flex items-center gap-1.5', props.class)">
<span
aria-hidden="true"
:class="
cn(
'h-2 w-2 shrink-0 rounded-full',
props.online ? 'bg-newt-online' : 'bg-newt-offline'
)
"
/>
<slot />
</span>
</template>export { default as Invite } from "./Invite.vue"
export { default as InviteEyebrow } from "./InviteEyebrow.vue"
export { default as InviteBody } from "./InviteBody.vue"
export { default as InviteIcon } from "./InviteIcon.vue"
export { default as InviteInfo } from "./InviteInfo.vue"
export { default as InviteName } from "./InviteName.vue"
export { default as InviteCounts } from "./InviteCounts.vue"
export { default as InviteCount } from "./InviteCount.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 { Invite } from "@/components/ui/invite"
export function Example() {
return <Invite />
}<script setup lang="ts">
import { Invite, InviteEyebrow, InviteBody, InviteIcon, InviteInfo, InviteName, InviteCounts, InviteCount } from "@/components/ui/invite"
</script>
<template>
<Invite />
</template>API reference
InviteCount
online?boolean | undefined
Green dot for the online count, muted for the member total.
boolean | undefinedThe registry exports Invite, InviteEyebrow, InviteBody, InviteIcon, InviteInfo, InviteName, InviteCounts, InviteCount. Parts without a table accept the native attributes for the element they render; React uses className, while Vue uses class and fallthrough attributes.
Accessibility
Use descriptive alternative text for a meaningful server image and an empty alt for a decorative one. The join action must remain a real button.
Tokens
Invite 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.