Skip to content

SecretInput

A secure input for sensitive values like API keys, passwords, and tokens. Values are never rendered in the DOM — they're masked with bullet characters and managed through a secure edit/reveal/copy workflow.

The input is read-only by default. Users click the edit (pencil) button to enter edit mode, and can optionally reveal the masked value or copy it to clipboard. Both show_toggle and show_copy are enabled by default.

API Key:

Action buttons (left-to-right): Edit (pencil) enters edit mode, Copy (clipboard) copies value, Reveal (eye) toggles mask on/off.

Edit Mode

Clicking the edit button hides the masked input and replaces it with a resizable textarea — always empty (the current secret is never pre-filled). Confirm (✓) and cancel (✗) buttons appear overlaid at the top-right corner of the textarea.

  • ConfirmCtrl+Enter or click ✓ — transmits the new value (base64-encoded) and restores the mask.
  • CancelEscape or click ✗ — discards the input and restores the previous mask.
API Key:

Basic Usage

from pywry import SecretInput

api_key = SecretInput(
    label="API Key",
    event="auth:api_key",
    placeholder="Enter API key",
)

Both show_toggle and show_copy default to True, so a basic SecretInput already has all three action buttons (edit, copy, reveal).

Disabling Action Buttons

# Only the edit button — no reveal or copy
SecretInput(
    label="Password",
    event="auth:password",
    show_toggle=False,  # Hide the reveal/eye button
    show_copy=False,    # Hide the copy button
)

With Custom Handler

For secrets stored in an external vault or environment variable:

import os
from pywry import SecretInput

def resolve_api_key(value, *, component_id, event, label=None, **metadata):
    """Custom handler for API key storage.

    Parameters:
        value: None to get the secret, str to set the secret
        component_id: unique component ID
        event: the event string
        label: optional label text
    """
    if value is None:
        # Get mode - return the secret
        return os.environ.get("MY_API_KEY", "")
    # Set mode - store the secret
    os.environ["MY_API_KEY"] = value
    return value

SecretInput(
    label="API Key",
    event="config:api_key",
    handler=resolve_api_key,
    value_exists=bool(os.environ.get("MY_API_KEY")),
)

Parameters

Parameter Type Default Description
label str "" Display label
event str "toolbar:input" Event name emitted on interaction
value SecretStr "" The secret value (never rendered in DOM)
placeholder str "" Placeholder text shown when empty
show_toggle bool True Show the eye reveal/hide button
show_copy bool True Show the copy-to-clipboard button
debounce int 300 Debounce delay in ms for input events
handler callable None Custom secret storage handler
value_exists bool None Flag indicating a value exists externally
component_id str auto Unique ID for state tracking
description str "" Tooltip/hover text
disabled bool False Disable interaction
style str "" Optional inline CSS

Events

Emits the event name with payload on edit (when user exits the textarea):

{"value": "<base64-encoded-string>", "encoded": true, "componentId": "secret-abc123"}
  • value — the new secret value, base64-encoded
  • encoded — always true (value must be decoded on the Python side)

Additional events:

  • {event}:copy — emitted when the copy button is clicked: {"componentId": "secret-abc123"}
    The backend responds on {event}:copy-response with the decrypted value.
  • {event}:reveal — emitted when the show/hide toggle is clicked: {"componentId": "secret-abc123"}
    The backend responds on {event}:reveal-response with the decrypted value.

Common Patterns

API Configuration

config_toolbar = Toolbar(
    position="top",
    items=[
        SecretInput(
            label="API Key",
            event="config:api_key",
            placeholder="sk-...",
        ),
        SecretInput(
            label="API Secret",
            event="config:api_secret",
            placeholder="Enter secret",
        ),
        Button(label="Save", event="config:save", variant="primary"),
    ],
)

Token Display

For displaying generated tokens:

import secrets
from pywry import PyWry, Toolbar, SecretInput, Button

app = PyWry()

def on_generate_token(data, event_type, label):
    token = secrets.token_urlsafe(32)
    app.emit("toolbar:set-value", {
        "componentId": "token-display",
        "value": token
    }, label)
    app.emit("pywry:alert", {"message": "New token generated!", "type": "success"}, label)

app.show(
    "<h1>Token Generator</h1>",
    toolbars=[
        Toolbar(position="top", items=[
            SecretInput(
                component_id="token-display",
                label="Token",
                event="token:value",
            ),
            Button(label="Generate New", event="token:generate"),
        ])
    ],
    callbacks={"token:generate": on_generate_token},
)

Login Form

login_modal = Modal(
    component_id="login-modal",
    title="Login",
    items=[
        TextInput(label="Username", event="login:username"),
        SecretInput(
            label="Password",
            event="login:password",
            show_copy=False,  # Don't need copy for password entry
        ),
        Button(label="Login", event="login:submit", variant="primary"),
    ],
)

Security Notes

Client-Side Only

SecretInput masks display only. For true security:

  • Never log secret values
  • Use HTTPS in production
  • Store secrets server-side when possible
  • Consider environment variables for sensitive configs

API Reference

Bases: ToolbarItem

A password/secret input field with visibility toggle and copy button.

Displays a masked input by default with icons on the right side: - Eye icon to toggle visibility (requests secret from backend) - Copy icon to copy value to clipboard (requests secret from backend)

Icons appear on hover/focus and input text is padded to not overlap them.

SECURITY: The secret value is NEVER rendered in HTML. When the user clicks the show or copy buttons, JavaScript emits events to request the secret from the backend. The backend must handle these events and respond with the actual value. This ensures secrets are only transmitted on explicit user action and never embedded in the DOM.

Events emitted: - {event} - On input change: {value, componentId} - {event}:reveal - On show click: {componentId} - backend should respond with secret - {event}:copy - On copy click: {componentId} - backend should respond with secret

Attributes:

Name Type Description
value SecretStr

Secret value stored as SecretStr (NEVER rendered in HTML).

placeholder str

Placeholder text shown when empty (default: "").

debounce int

Milliseconds to debounce input events (default: 300).

show_toggle bool

Show the visibility toggle button (default: True).

show_copy bool

Show the copy to clipboard button (default: True).

handler callable or None

Optional callable for custom secret storage. Signature: (value, *, component_id, event, label, **metadata) -> str | None. Pass None to get the secret, a string to set it. If not provided, uses internal SecretStr storage.

value_exists bool or None

Flag indicating a value exists externally. If None, computed from value.

Examples:

Simple usage with internal storage:

>>> SecretInput(
...     label="API Key:",
...     event="settings:api_key",
...     value="my-secret",
... )

Custom handler with component metadata:

>>> def api_key_handler(
...     value,
...     *,
...     component_id,
...     event,
...     label=None,
...     **metadata,
... ):
...     if value is None:
...         return secrets_manager.get(component_id)
...     secrets_manager.set(component_id, value)
...     return value
>>> SecretInput(
...     event="settings:api_key",
...     handler=api_key_handler,
... )

Attributes

has_value property

has_value: bool

Check if a secret value is set (without exposing it).

Returns True if: - value_exists is explicitly True, OR - value_exists is None and internal value is non-empty

Functions

build_html

build_html() -> str

Build secret input HTML with visibility toggle and copy button.

When a value exists, a fixed mask (••••••••••••) is shown. Show/copy buttons emit events to request the real secret from the backend. Values are base64 encoded in transit for obfuscation.

Edit mode: - Click on edit button to enter edit mode - Switches to a resizable textarea (no wrapping, no formatting) - Confirm with blur or Ctrl+Enter - Cancel with Escape (restores mask) - Only transmits on confirm, not during typing

register

register() -> None

Register this SecretInput in the secret registry.

Called automatically by Toolbar when building HTML. If a custom handler is provided, it's registered for reveal/copy events. Otherwise, the internal value is registered for retrieval.

update_secret

update_secret(new_value: str | SecretStr, encoded: bool = False) -> None

Update the stored secret value.

Use this when receiving a new value from user input. If a custom handler is configured, it will be called to store the value.

Parameters:

Name Type Description Default
new_value str | SecretStr

The new secret value. If encoded=True and this is a string, it will be base64-decoded first.

required
encoded bool

Whether the value is base64-encoded (for transit obfuscation).

False

get_reveal_event

get_reveal_event() -> str

Get the event type for reveal requests.

get_reveal_response_event

get_reveal_response_event() -> str

Get the event type for reveal responses.

get_copy_event

get_copy_event() -> str

Get the event type for copy requests.

get_copy_response_event

get_copy_response_event() -> str

Get the event type for copy responses.

get_secret_value

get_secret_value() -> str | None

Get the current secret value.

If a custom handler is configured, calls handler with metadata. Otherwise, returns the internal value or from the registry.

Returns:

Type Description
str | None

The secret value, or None if not set.

auto_generate_component_id

auto_generate_component_id() -> ToolbarItem

Auto-generate component_id based on type if not provided.

validate_event_name classmethod

validate_event_name(v: str) -> str

Validate event follows namespace:event-name pattern.