Permission Row
A settings row with a deny / inherit / allow tri-state toggle.
A settings row with a deny / inherit / allow tri-state toggle. Compose it from the exported registry parts so local content and behaviour stay in the application while the visual contract stays token-driven.
newt-permissionimport {
Permission,
PermissionDescription,
PermissionInfo,
PermissionName,
PermissionStates,
} from "@/registry/bases/newt/ui/permission"
export default function PermissionDemo() {
return (
<div className="w-full max-w-md">
<Permission>
<PermissionInfo>
<PermissionName>Permission name</PermissionName>
<PermissionDescription>Permission description.</PermissionDescription>
</PermissionInfo>
<PermissionStates aria-label="Permission name" defaultValue="inherit" />
</Permission>
</div>
)
}<script setup lang="ts">
import { ref } from "vue"
import {
Permission,
PermissionDescription,
PermissionInfo,
PermissionName,
PermissionStates,
type PermissionValue,
} from "~~/registry/bases/newt/ui/permission"
const value = ref<PermissionValue>("inherit")
</script>
<template>
<div class="w-full max-w-md">
<Permission>
<PermissionInfo>
<PermissionName>Permission name</PermissionName>
<PermissionDescription>Permission description.</PermissionDescription>
</PermissionInfo>
<PermissionStates v-model="value" aria-label="Permission name" />
</Permission>
</div>
</template>Installation
pnpm dlx newtui@latest add permissionnpx newtui@latest add permissionyarn dlx newtui@latest add permissionbunx newtui@latest add permissionCopy the source into your project
"use client"
import * as React from "react"
import { cn } from "@/lib/utils"
type PermissionValue = "deny" | "inherit" | "allow"
const Permission = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
"flex items-center justify-between border-b border-newt-border py-2.5 last:border-b-0",
className
)}
{...props}
/>
))
Permission.displayName = "Permission"
const PermissionInfo = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex flex-col gap-0.5", className)}
{...props}
/>
))
PermissionInfo.displayName = "PermissionInfo"
const PermissionName = React.forwardRef<
HTMLSpanElement,
React.HTMLAttributes<HTMLSpanElement>
>(({ className, ...props }, ref) => (
<span
ref={ref}
className={cn("text-sm font-medium text-newt-text-primary", className)}
{...props}
/>
))
PermissionName.displayName = "PermissionName"
const PermissionDescription = React.forwardRef<
HTMLSpanElement,
React.HTMLAttributes<HTMLSpanElement>
>(({ className, ...props }, ref) => (
<span
ref={ref}
className={cn("text-xs text-newt-text-muted", className)}
{...props}
/>
))
PermissionDescription.displayName = "PermissionDescription"
const stateOptions: { value: PermissionValue; label: string; glyph: string }[] =
[
{ value: "deny", label: "Deny", glyph: "✕" },
{ value: "inherit", label: "Inherit", glyph: "/" },
{ value: "allow", label: "Allow", glyph: "✓" },
]
const activeStateClasses = {
deny: "bg-[rgba(242,63,66,0.15)] text-newt-dnd hover:text-newt-dnd",
inherit: "bg-newt-bg-elevated text-newt-text-secondary",
allow: "bg-[rgba(35,165,90,0.15)] text-newt-online hover:text-newt-online",
} satisfies Record<PermissionValue, string>
export interface PermissionStatesProps extends Omit<
React.HTMLAttributes<HTMLDivElement>,
"defaultValue" | "onChange"
> {
/** Controlled value. */
value?: PermissionValue
/** Initial value when uncontrolled. Defaults to `"inherit"`. */
defaultValue?: PermissionValue
onValueChange?: (value: PermissionValue) => void
disabled?: boolean
}
const PermissionStates = React.forwardRef<
HTMLDivElement,
PermissionStatesProps
>(
(
{
className,
value: valueProp,
defaultValue = "inherit",
onValueChange,
disabled = false,
...props
},
ref
) => {
const [internal, setInternal] =
React.useState<PermissionValue>(defaultValue)
const isControlled = valueProp !== undefined
const value = isControlled ? valueProp : internal
const select = (next: PermissionValue) => {
if (disabled) return
if (!isControlled) setInternal(next)
onValueChange?.(next)
}
return (
<div
ref={ref}
role="group"
className={cn("flex gap-1 rounded-sm bg-newt-bg-base p-0.5", className)}
{...props}
>
{stateOptions.map((option) => {
const active = option.value === value
return (
<button
key={option.value}
type="button"
aria-pressed={active}
aria-label={option.label}
disabled={disabled}
data-state={active ? "active" : "inactive"}
data-value={option.value}
onClick={() => select(option.value)}
className={cn(
"flex h-7 w-7 cursor-pointer items-center justify-center rounded-sm border-0 bg-transparent text-sm text-newt-text-muted transition-colors duration-fast ease-newt hover:text-newt-text-secondary disabled:cursor-not-allowed disabled:opacity-50",
active && activeStateClasses[option.value]
)}
>
{option.glyph}
</button>
)
})}
</div>
)
}
)
PermissionStates.displayName = "PermissionStates"
export {
Permission,
PermissionInfo,
PermissionName,
PermissionDescription,
PermissionStates,
type PermissionValue,
}<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 items-center justify-between border-b border-newt-border py-2.5 last:border-b-0',
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 flex-col gap-0.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>
<span :class="cn('text-sm font-medium text-newt-text-primary', 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>
<span :class="cn('text-xs text-newt-text-muted', props.class)">
<slot />
</span>
</template><script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"
import type { PermissionValue } from "."
const props = defineProps<{
class?: HTMLAttributes["class"]
disabled?: boolean
}>()
const model = defineModel<PermissionValue>({ default: "inherit" })
const options: { value: PermissionValue; label: string; glyph: string }[] = [
{ value: "deny", label: "Deny", glyph: "✕" },
{ value: "inherit", label: "Inherit", glyph: "/" },
{ value: "allow", label: "Allow", glyph: "✓" },
]
const activeClasses = {
deny: "bg-[rgba(242,63,66,0.15)] text-newt-dnd hover:text-newt-dnd",
inherit: "bg-newt-bg-elevated text-newt-text-secondary",
allow: "bg-[rgba(35,165,90,0.15)] text-newt-online hover:text-newt-online",
} satisfies Record<PermissionValue, string>
function select(value: PermissionValue) {
if (props.disabled) return
model.value = value
}
</script>
<template>
<div
role="group"
:class="cn('flex gap-1 rounded-sm bg-newt-bg-base p-0.5', props.class)"
>
<button
v-for="option in options"
:key="option.value"
type="button"
:aria-pressed="option.value === model"
:aria-label="option.label"
:disabled="props.disabled"
:data-state="option.value === model ? 'active' : 'inactive'"
:data-value="option.value"
:class="
cn(
'flex h-7 w-7 cursor-pointer items-center justify-center rounded-sm border-0 bg-transparent text-sm text-newt-text-muted transition-colors duration-fast ease-newt hover:text-newt-text-secondary disabled:cursor-not-allowed disabled:opacity-50',
option.value === model && activeClasses[option.value]
)
"
@click="select(option.value)"
>
{{ option.glyph }}
</button>
</div>
</template>export { default as Permission } from "./Permission.vue"
export { default as PermissionInfo } from "./PermissionInfo.vue"
export { default as PermissionName } from "./PermissionName.vue"
export { default as PermissionDescription } from "./PermissionDescription.vue"
export { default as PermissionStates } from "./PermissionStates.vue"
export type PermissionValue = "deny" | "inherit" | "allow"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 { Permission } from "@/components/ui/permission"
export function Example() {
return <Permission />
}<script setup lang="ts">
import { Permission, PermissionInfo, PermissionName, PermissionDescription, PermissionStates } from "@/components/ui/permission"
</script>
<template>
<Permission />
</template>API reference
PermissionStates
value?PermissionValue | undefined
Controlled value.
PermissionValue | undefineddefaultValue?PermissionValue | undefined
Initial value when uncontrolled. Defaults to `"inherit"`.
PermissionValue | undefinedonValueChange?(value: PermissionValue) => void | undefined
Optional onValueChange value.
(value: PermissionValue) => void | undefineddisabled?boolean | undefined
Optional disabled value.
boolean | undefinedThe registry exports Permission, PermissionInfo, PermissionName, PermissionDescription, PermissionStates, PermissionValue. Parts without a table accept the native attributes for the element they render; React uses className, while Vue uses class and fallthrough attributes.
Accessibility
Expose deny, inherit and allow as one labelled choice group. The selected state must be announced programmatically and not communicated by colour alone.
Tokens
Permission 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-permission.