Source: https://monarchui.dev/components/secret-field/

Components · Technical infrastructure

# SecretField

A masked secret with reveal and hide, optional copy, and a "set, not shown" state.

@rookcom/monarch-react

Component

## Shown once, with copy

## Usage examples

### Set, not shown

### Sizes, standalone

## Purpose

Enters or shows a secret (API key, bot token, signing secret) masked by default, with a toggle to reveal it and an optional copy button. The "set, not shown" state covers secrets the server stores but never sends back.

### Use when

- A settings form takes a credential for an integration.
- A newly created key is shown once and the user must copy it.
- The server holds a saved secret: show that one exists, and let the user replace it.

### Avoid when

- The user signs in: sign-in belongs to the identity flow, not a settings field.
- The value is not sensitive: use CopyField or Input.

## Design principles

- Masked until the user asks. Revealing is a deliberate, reversible toggle.
- Copy never needs reveal: the copy button copies the value while it stays masked.
- Honest about saved secrets: an `isSet` field is empty, says a value is saved, and offers nothing to reveal or copy, because the browser does not have it.
- Kept out of password managers and autocorrect, so a token is never saved as a login or "corrected".

## API reference

```tsx
import { SecretField } from '@rookcom/monarch-react';
```

| Export | Kind | Rendering | Root class |
| --- | --- | --- | --- |
| `SecretField` | component | client | `.mn-secret-` |

Forwards its ref to the `<input>`. All Input props pass through except `type`, which the reveal state controls.

Works inside a Field: the FieldLabel names the input, and Field descriptions and errors merge with the `isSet` description.

Generated from the TypeScript declarations of `packages/react/src/components/technical/secret-field`.

## Properties

`value`/`onChange` (controlled) or `defaultValue` (uncontrolled); copy uses the current text in both modes.

`revealed`/`onRevealedChange` (controlled) or `defaultRevealed` (uncontrolled).

### `SecretField`

A masked secret (API key, token, signing secret) with reveal/hide and an optional copy button. Never prefilled by the browser or password managers. Supports the "set, not shown" state for secrets the server never returns.

| Prop | Type | Description |
| --- | --- | --- |
| `label` required | `string` | What the secret is ("API key", "Bot token"). Names the reveal and copy buttons ("Show API key") and, outside a Field, the text box. |
| `children` | `ReactNode` |  |
| `className` | `string` |  |
| `copyable` | `boolean` | Show a copy button. It copies the current value without revealing it. Default false. |
| `defaultRevealed` | `boolean` | Uncontrolled initial value of `revealed`. Default false. |
| `disabled` | `boolean` |  |
| `id` | `string` |  |
| `invalid` | `boolean` | Marks the value invalid. Inside a Field, the Field's `invalid` is used. |
| `isSet` | `boolean` | A value is saved on the server but is never sent back, so there is nothing to reveal or copy. The field stays empty, says the secret is set, and typing a new value replaces it; leaving it blank keeps the saved one. |
| `onCopy` | `((text: string) => void)` | Called with the copied text after a successful copy. |
| `onCopyError` | `((error: Error) => void)` | Called when copying fails. |
| `onRevealedChange` | `((revealed: boolean) => void)` |  |
| `revealable` | `boolean` | Show the reveal/hide toggle. Default true. |
| `revealed` | `boolean` | Controlled: whether the secret is shown in plain text. |
| `setDescription` | `string` | Screen reader description while `isSet`. Default "A value is saved and hidden. Leave blank to keep it." |
| `setPlaceholder` | `string` | Placeholder while `isSet` and empty. Default "Saved. Enter a new value to replace it". |
| `size` | `ControlSize` |  |

## Variants

| Prop | Values | Description |
| --- | --- | --- |
| `size` | `'sm'``'md'``'lg'` | Input size; the inner buttons scale with it. |
| `revealable` | `'true'``'false'` | Show the reveal toggle. Default true. |
| `copyable` | `'true'``'false'` | Show a copy button. Default false. |

## States

| State | Exposed as | Behaviour |
| --- | --- | --- |
| Masked | `type="password"` | Default. The toggle is `aria-pressed="false"`. |
| Revealed | `data-revealed, type="text", aria-pressed="true"` | Plain text; the toggle shows the eye-off icon. |
| Set, not shown | `data-set` | Empty with the `setPlaceholder`; described as saved and hidden. Reveal and copy are disabled until the user types a replacement. |
| Empty | `disabled on the inner buttons` | Nothing to reveal or copy yet. |
| Invalid | `aria-invalid` | From Field or the `invalid` prop. |
| Disabled / read-only | `disabled, readonly` | Native input states. |

## Interaction behaviour

- The eye button toggles between masked and plain text.
- The copy button copies the current value (typed or given) and confirms like CopyButton.
- With `isSet`, leaving the field blank keeps the saved secret: the product sends nothing. Typing a value replaces it.

## Keyboard support

| Keys | Action |
| --- | --- |
| Tab | Input, then the reveal toggle, then the copy button. |
| Enter / Space | On the toggle: reveal or hide. On the copy button: copy. |

## Accessibility

Role

textbox (password), toggle button, button

Accessible name

Outside a Field,

label

is the input’s

aria-label

; inside one, the FieldLabel. The toggle is "Show {label}" with

aria-pressed

, the copy button "Copy {label}".

- The toggle keeps one name and reports its state with `aria-pressed`, as the WAI-ARIA toggle-button pattern requires.
- The `isSet` state is in `aria-describedby`, not only the placeholder.
- The copy result is announced by CopyButton’s live region.
- Inner buttons are at least 28px, above the WCAG 2.2 target-size minimum.

## Responsive behaviour

- Full width of its container; the buttons sit inside the input’s end edge and the text is padded to clear them.
- Logical properties throughout, so the buttons move to the left in right-to-left layouts.

## Theme support

- Built from Input and IconButton, so every theme and more-contrast mode applies.
- The value uses `--mn-font-mono`; the placeholder stays in the sans font.

## Do and don't

### Do

- Use `isSet` for saved secrets and send a new value only when the user typed one.
- Pair a one-time secret with `copyable` and a description that says it will not be shown again.
- Keep validation and storage in the product; SecretField only masks and copies.

### Don't

- Do not prefill a saved secret into the field; the server should never return it.
- Do not reveal by default.
- Do not use it for sign-in passwords.

## Related components

- [CopyField](https://monarchui.dev/components/copy-field/): For values that are not secret.
- [CopyButton](https://monarchui.dev/components/copy-button/): The copy button it uses.
- [Input](https://monarchui.dev/components/text-field/): The Input it renders.
- [Field](https://monarchui.dev/components/form-field/): Label, help and errors.
- [Alert](https://monarchui.dev/components/alert/): For a prominent "copy it now" warning around a one-time secret.

## Dependencies

| Kind | Depends on |
| --- | --- |
| npm packages | `react` |
| Monarch components | [Button](https://monarchui.dev/components/button/), [CopyButton](https://monarchui.dev/components/copy-button/), [Field](https://monarchui.dev/components/form-field/), [Input](https://monarchui.dev/components/text-field/) |

## Testing coverage

- Masked by default; the toggle reveals and hides with a stable name and `aria-pressed`.
- Toggles from the keyboard.
- Controlled reveal and controlled value.
- Copies the current value without revealing it, in controlled and uncontrolled modes.
- "Set, not shown": empty, described, reveal and copy disabled until a new value is typed.
- Turns off autocomplete, spell-check and autocapitalise, and marks the input for password managers to ignore.
- Inside a Field: FieldLabel names it, descriptions merge, invalid applies.
- Forwards the ref to the input; `revealable={false}` hides the toggle.

| Check | Covered |
| --- | --- |
| Axe in jsdom | Yes |
| Server rendering | Yes |
| Browser suites | Visual baselines: /__gallery; Cross-browser axe: /components/secret-field |

Tests: `packages/react/src/components/technical/secret-field`

## All example code

### Shown once, with copy

```tsx
import { Field, FieldDescription, FieldLabel, SecretField } from '@rookcom/monarch-react';

const narrow = { width: '100%', maxWidth: '28rem' } as const;

function NewKey() {
  return (
    <Field style={narrow}>
      <FieldLabel>Signing secret</FieldLabel>
      <SecretField label="Signing secret" defaultValue="whsec_9d2f7a61c0b84e3f" readOnly copyable />
      <FieldDescription>
        Shown once. Store it somewhere safe before you leave this page.
      </FieldDescription>
    </Field>
  );
}

<NewKey />
```

### Set, not shown

```tsx
import { useState } from 'react';
import { Field, FieldDescription, FieldLabel, SecretField } from '@rookcom/monarch-react';

const narrow = { width: '100%', maxWidth: '28rem' } as const;

function SavedToken() {
  const [value, setValue] = useState('');
  return (
    <Field style={narrow}>
      <FieldLabel>API token</FieldLabel>
      <SecretField
        label="API token"
        isSet
        value={value}
        onChange={(e) => setValue(e.target.value)}
      />
      <FieldDescription>
        {value ? 'The new token replaces the saved one when you save.' : 'Stored encrypted.'}
      </FieldDescription>
    </Field>
  );
}

<SavedToken />
```

### Sizes, standalone

```tsx
import { SecretField } from '@rookcom/monarch-react';

const narrow = { width: '100%', maxWidth: '28rem' } as const;

<div style={{ ...narrow, display: 'grid', gap: 'var(--mn-space-3)' }}>
  <SecretField label="Bot token" size="sm" defaultValue="bot-4f1a9c" />
  <SecretField label="Access key" size="lg" defaultValue="AKIA4EXAMPLE7Q" copyable />
</div>
```
