Token Field
A read-only, masked monospace input with reveal and copy-to-clipboard actions for secrets such as bot tokens.
A read-only, masked monospace input with reveal and copy-to-clipboard actions for secrets such as bot tokens. Compose it from the exported registry parts so local content and behaviour stay in the application while the visual contract stays token-driven.
newt-token-fieldimport { Label } from "@/registry/bases/newt/ui/form-fields"
import { TokenField } from "@/registry/bases/newt/ui/token-field"
export default function TokenFieldDemo() {
return (
<div className="flex w-full max-w-md flex-col gap-2">
<Label htmlFor="newt-token-example">Bot token</Label>
<TokenField
id="newt-token-example"
value="MTA4NzY1NDMyMTA5ODc2NTQzMjE.GxYzAb.c0d3-s3cr3t-t0k3n"
/>
</div>
)
}<script setup lang="ts">
import { Label } from "~~/registry/bases/newt/ui/form-fields"
import { TokenField } from "~~/registry/bases/newt/ui/token-field"
</script>
<template>
<div class="flex w-full max-w-md flex-col gap-2">
<Label for="newt-token-example">Bot token</Label>
<TokenField
id="newt-token-example"
value="MTA4NzY1NDMyMTA5ODc2NTQzMjE.GxYzAb.c0d3-s3cr3t-t0k3n"
/>
</div>
</template>Installation
pnpm dlx newtui@latest add token-fieldnpx newtui@latest add token-fieldyarn dlx newtui@latest add token-fieldbunx newtui@latest add token-fieldAdd the components it builds on
Copy the source into your project
"use client"
import * as React from "react"
import { cn } from "@/lib/utils"
import { Button } from "@/registry/bases/newt/ui/button"
import { Input } from "@/registry/bases/newt/ui/form-fields"
export interface TokenFieldProps extends Omit<
React.ComponentProps<"div">,
"children"
> {
/** The secret value. Masked until revealed. */
value: string
/** Id forwarded to the input so a label can target it. */
id?: string
/** Accessible name for the input when no visible label is linked. */
name?: string
/** Label of the reveal button while hidden. */
revealLabel?: string
/** Label of the reveal button while shown. */
hideLabel?: string
/** Label of the copy button. */
copyLabel?: string
/** Label of the copy button right after copying. */
copiedLabel?: string
/** How long (ms) the copied label is shown. */
copiedDuration?: number
/** Called after the value is written to the clipboard. */
onCopy?: () => void
}
const TokenField = React.forwardRef<HTMLDivElement, TokenFieldProps>(
(
{
className,
value,
id,
name,
revealLabel = "Reveal",
hideLabel = "Hide",
copyLabel = "Copy",
copiedLabel = "Copied!",
copiedDuration = 1500,
onCopy,
...props
},
ref
) => {
const [revealed, setRevealed] = React.useState(false)
const [copied, setCopied] = React.useState(false)
const timeoutRef = React.useRef<ReturnType<typeof setTimeout> | null>(null)
React.useEffect(
() => () => {
if (timeoutRef.current) clearTimeout(timeoutRef.current)
},
[]
)
const handleCopy = async () => {
await navigator.clipboard.writeText(value)
onCopy?.()
setCopied(true)
if (timeoutRef.current) clearTimeout(timeoutRef.current)
timeoutRef.current = setTimeout(() => setCopied(false), copiedDuration)
}
return (
<div ref={ref} className={cn("flex gap-2", className)} {...props}>
<Input
id={id}
name={name}
type={revealed ? "text" : "password"}
value={value}
readOnly
className="font-mono tracking-[0.05em]"
/>
<Button
type="button"
variant="secondary"
aria-pressed={revealed}
onClick={() => setRevealed((r) => !r)}
>
{revealed ? hideLabel : revealLabel}
</Button>
<Button
type="button"
variant="secondary"
aria-live="polite"
onClick={() => void handleCopy()}
>
{copied ? copiedLabel : copyLabel}
</Button>
</div>
)
}
)
TokenField.displayName = "TokenField"
export { TokenField }<script setup lang="ts">
import { onBeforeUnmount, ref, type HTMLAttributes } from "vue"
import { Button } from "~~/registry/bases/newt/ui/button"
import { Input } from "~~/registry/bases/newt/ui/form-fields"
import { cn } from "@/lib/utils"
const props = withDefaults(
defineProps<{
/** The secret value. Masked until revealed. */
value: string
/** Id forwarded to the input so a label can target it. */
id?: string
name?: string
revealLabel?: string
hideLabel?: string
copyLabel?: string
copiedLabel?: string
/** How long (ms) the copied label is shown. */
copiedDuration?: number
class?: HTMLAttributes["class"]
}>(),
{
revealLabel: "Reveal",
hideLabel: "Hide",
copyLabel: "Copy",
copiedLabel: "Copied!",
copiedDuration: 1500,
}
)
const emit = defineEmits<{
(e: "copy"): void
}>()
const revealed = ref(false)
const copied = ref(false)
let timeout: ReturnType<typeof setTimeout> | null = null
async function copy() {
await navigator.clipboard.writeText(props.value)
emit("copy")
copied.value = true
if (timeout) clearTimeout(timeout)
timeout = setTimeout(() => (copied.value = false), props.copiedDuration)
}
onBeforeUnmount(() => {
if (timeout) clearTimeout(timeout)
})
</script>
<template>
<div :class="cn('flex gap-2', props.class)">
<Input
:id="props.id"
:name="props.name"
:type="revealed ? 'text' : 'password'"
:value="props.value"
readonly
class="font-mono tracking-[0.05em]"
/>
<Button
type="button"
variant="secondary"
:aria-pressed="revealed"
@click="revealed = !revealed"
>
{{ revealed ? props.hideLabel : props.revealLabel }}
</Button>
<Button type="button" variant="secondary" aria-live="polite" @click="copy">
{{ copied ? props.copiedLabel : props.copyLabel }}
</Button>
</div>
</template>export { default as TokenField } from "./TokenField.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 { TokenField } from "@/components/ui/token-field"
export function Example() {
return <TokenField />
}<script setup lang="ts">
import { TokenField } from "@/components/ui/token-field"
</script>
<template>
<TokenField />
</template>API reference
TokenField
value?string
The secret value. Masked until revealed.
stringid?string | undefined
Id forwarded to the input so a label can target it.
string | undefinedname?string | undefined
Accessible name for the input when no visible label is linked.
string | undefinedrevealLabel?string | undefined
Label of the reveal button while hidden.
string | undefinedhideLabel?string | undefined
Label of the reveal button while shown.
string | undefinedcopyLabel?string | undefined
Label of the copy button.
string | undefinedcopiedLabel?string | undefined
Label of the copy button right after copying.
string | undefinedcopiedDuration?number | undefined
How long (ms) the copied label is shown.
number | undefinedonCopy?() => void | undefined
Called after the value is written to the clipboard.
() => void | undefinedThe registry exports TokenField. Parts without a table accept the native attributes for the element they render; React uses className, while Vue uses class and fallthrough attributes.
Accessibility
Label the token input, announce copy feedback through a polite live region, and never make the reveal or copy actions icon-only without an accessible name.
Tokens
Token Field 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-token-field.