KKiotaPay UI
Browse documentation

Copy Field

A read-only value with a copy button, and optional masking for secrets. Built on the shadcn Button and the useCopy hook.

Installation

pnpm dlx shadcn@latest add https://kiotapay-ui-staging.staging.kiotapay.com/r/copy-field.json

This item installs three files — the component, the use-copy hook it depends on, and kiota-theme — plus the shadcn button primitive. It is the clearest example in this registry of one item pulling in a whole small tree.

Usage

import { CopyField } from '@/components/custom/copy-field'

export function ReferenceRow() {
  return <CopyField label="Payment reference" value="KP-9F2A41B7-2026" />
}

Props

Extends React.ComponentProps<'div'> minus onCopy.

PropTypeDefaultDescription
valuestring—The full value. Always copied in full, even while masked.
labelReactNode—Optional caption above the field.
maskedbooleanfalseHide the value behind dots until revealed.
visibleCharsnumber4Trailing characters left readable while masked.
onCopied(success: boolean) => void—Called after a copy attempt with the result.

Examples

Masked secrets

Reveal is a toggle, not a one-way door — the value can be hidden again without a re-render of the surrounding page.

Behaviour notes

Copy always writes the real value. Masking is a shoulder-surfing guard, not access control. Requiring a reveal before copying would add friction without adding security, and mostly trains people to leave secrets visible on screen.

The clipboard write has a fallback. navigator.clipboard is missing on insecure origins and rejects when the document is unfocused. useCopy tries the async API first and falls through to document.execCommand whenever it fails, rather than only when it is absent.

Copy can still fail. If both paths fail, onCopied receives false and no checkmark appears — the confirmation never claims success it did not get.

<CopyField
  value={apiKey}
  masked
  onCopied={(success) => {
    if (!success) toast.error('Could not access the clipboard')
  }}
/>

Accessibility

The masked display is visual only — screen readers get the real value through aria-label, since dots convey nothing useful when read aloud. Copy confirmation is announced through an aria-live="polite" region, so keyboard and screen-reader users get the same feedback the checkmark gives sighted users. The reveal toggle carries aria-pressed to expose its state.

This component renders whatever string it is handed. Masking does not prevent the value reaching the browser — if a secret should not be readable by the client at all, do not send it to the client.