Pin input
Use CPinInput for one-time codes, PINs, and short recovery tokens. Its value is always a string, so a leading zero is preserved.
Enter a verification code
Give a standalone PinInput an accessible label, or place it in CField for a visible label, help, error, and shared state.
<c-CField required>
<c-fill name="label">Verification code</c-fill>
<c-fill name="description">Enter the six digits from your message.</c-fill>
<c-fill name="default"><c-CPinInput name="code" /></c-fill>
</c-CField>
Show code
from citry import Component
class BasicPinInput(Component):
class Kwargs:
pass
class Slots:
pass
template = """
<c-CField required>
<c-fill name="label">Verification code</c-fill>
<c-fill name="description">Enter the six digits from your message.</c-fill>
<c-fill name="default"><c-CPinInput name="code" /></c-fill>
</c-CField>
"""
preview = BasicPinInput()
preview # noqa: B018
One native text input owns focus, selection, paste, autofill, validation, and submission. The separate cells are visual only and create neither extra Tab stops nor separate Form values. Without JavaScript the native input remains a normal usable text box.
Accept recovery-code letters
The default type="numeric" accepts ASCII digits. Use alphabetic or alphanumeric for protocol tokens containing ASCII letters. These values are opaque identifiers, not localized numbers.
Show code
from citry import Component
class AlphanumericPinInput(Component):
class Kwargs:
pass
class Slots:
pass
template = """
<c-CField>
<c-fill name="label">Recovery code</c-fill>
<c-fill name="description">Use the eight letters and digits printed with your account.</c-fill>
<c-fill name="default"><c-CPinInput name="recovery" type="alphanumeric" c-length="8" /></c-fill>
</c-CField>
"""
preview = AlphanumericPinInput()
preview # noqa: B018
Invalid characters are discarded and reported through onValueInvalid. length is structural and supports 1 through 32 characters.
Control the value
Client value controls the exact string. An edit is a request: the displayed cells and Form value remain owner-controlled until the Alpine expression returns the requested value.
Show code
from citry import Component
# ruff: noqa: E501 - Alpine expression stays readable in public source
class ControlledPinInput(Component):
class Kwargs:
pass
class Slots:
pass
template = """
<section class="pin-input-demo-stack" x-data="{code:'12',last:'No request yet'}">
<c-CPinInput
label="Controlled four-digit code"
value="12"
c-length="4"
$c-props="{value:code,onValueChange:(next,detail)=>{code=next;last=`${detail.source}: ${next}`},onComplete:(next)=>last=`Complete: ${next}`}"
/>
<output x-text="last">No request yet</output>
<c-CButton type="button" @click="code=''">Clear</c-CButton>
</section>
"""
css = ":where(.pin-input-demo-stack){display:grid;justify-items:start;gap:.75rem}"
preview = ControlledPinInput()
preview # noqa: B018
onValueChange reports accepted edits. onComplete reports a transition to a full value and never submits the Form automatically. Paste and autofill remain available.
Preserve native Form behavior
required combines with an exact-length native pattern, so an empty or partial required code blocks submission. Readonly values remain focusable and submit; disabled values do not submit.
Show code
from citry import Component
# ruff: noqa: E501 - template expression stays readable in public source
class PinInputForms(Component):
class Kwargs:
pass
class Slots:
pass
template = """
<form class="pin-input-demo-stack" x-data="{result:'Submit or reset the Form'}" @submit.prevent="result=JSON.stringify(Array.from(new FormData($event.target).entries()))">
<c-CField required>
<c-fill name="label">One-time code</c-fill>
<c-fill name="default"><c-CPinInput name="code" value="01" /></c-fill>
</c-CField>
<c-CPinInput name="issued" label="Issued code" value="246810" readonly />
<c-CGroup><c-CButton type="submit">Submit</c-CButton><c-CButton type="reset" variant="outline">Reset</c-CButton></c-CGroup>
<output x-text="result">Submit or reset the Form</output>
</form>
"""
css = ":where(.pin-input-demo-stack){display:grid;justify-items:start;gap:1rem}"
preview = PinInputForms()
preview # noqa: B018
one_time_code=True emits autocomplete="one-time-code". Set an explicit input_attrs={"autocomplete": "..."} when another autocomplete policy is required. Citry never invokes WebOTP or reads SMS messages.
Mask or group the visual cells
mask=True replaces filled visual cells with bullets without changing the submitted string. It reduces shoulder surfing but is not encryption and does not hide the accessible text-field value from assistive software.
Show code
from citry import Component
class MaskedPinInput(Component):
class Kwargs:
pass
class Slots:
pass
template = """
<section class="pin-input-demo-stack">
<c-CPinInput label="Private access code" name="access-code" value="7412" c-length="4" mask />
<p>Masking changes the visual cells only. Treat the submitted token as sensitive data.</p>
</section>
"""
css = ":where(.pin-input-demo-stack){display:grid;justify-items:start;gap:.75rem}"
preview = MaskedPinInput()
preview # noqa: B018
Use attached=True to join cells. For a 3β3 presentation, provide separator_after=(2,) and the separator slot. Separator output is visual; put instructions in Field description text.
Show code
from citry import Component
class SeparatedPinInput(Component):
class Kwargs:
pass
class Slots:
pass
template = """
<section class="pin-input-demo-stack">
<c-CPinInput label="Grouped recovery code" type="alphanumeric" c-separator_after="(2,)">
<c-fill name="separator" data="{ index }">-</c-fill>
</c-CPinInput>
<c-CPinInput label="Attached four-digit code" c-length="4" attached />
</section>
"""
css = ":where(.pin-input-demo-stack){display:grid;justify-items:start;gap:1rem}"
preview = SeparatedPinInput()
preview # noqa: B018
Keep code direction and locale ownership clear
PinInput renders protocol tokens left-to-right by default, including inside an RTL page. ASCII digits are not localized. Labels, Field text, placeholders, and separators belong to the application and stay in its locale.
Show code
from citry import Component
class PinInputLocales(Component):
class Kwargs:
pass
class Slots:
pass
template = """
<section class="pin-input-demo-stack" dir="rtl">
<c-CField>
<c-fill name="label">Ψ±Ω
Ψ² Ψ§ΩΨͺΨΩΩ</c-fill>
<c-fill name="description">ΩΨ¨ΩΩ Ψ±Ω
Ψ² Ψ§ΩΨ¨Ψ±ΩΨͺΩΩΩΩ Ω
Ω Ψ§ΩΩΨ³Ψ§Ψ± Ψ₯ΩΩ Ψ§ΩΩΩ
ΩΩ.</c-fill>
<c-fill name="default"><c-CPinInput value="104" /></c-fill>
</c-CField>
</section>
"""
css = ":where(.pin-input-demo-stack){display:grid;justify-items:start;gap:.75rem}"
preview = PinInputLocales()
preview # noqa: B018
Choose states and public styles
Outline and subtle variants combine with sm, md, and lg sizes. Public --cui-pin-input-* variables and documented part selectors customize cells, focus, separators, and state treatment.
Show code
from citry import Component
class PinInputStates(Component):
class Kwargs:
pass
class Slots:
pass
template = """
<section class="pin-input-state-grid">
<c-CPinInput label="Small subtle code" value="12" size="sm" variant="subtle" />
<c-CPinInput label="Default code" value="123" />
<c-CPinInput label="Large complete code" value="123456" size="lg" />
<c-CPinInput label="Readonly code" value="246810" readonly />
<c-CPinInput label="Disabled code" value="135790" disabled />
<c-CPinInput label="Invalid code" value="12" invalid class_="pin-input-brand" />
</section>
"""
css = """
:where(.pin-input-state-grid){display:grid;grid-template-columns:repeat(auto-fit,minmax(18rem,1fr));gap:1.5rem;align-items:start}
:where(.pin-input-brand){--cui-pin-input-focus-color:#7c3aed;--cui-pin-input-radius:.75rem}
"""
preview = PinInputStates()
preview # noqa: B018
Tab enters the component once. Native text editing and clipboard shortcuts continue to work; Home, End, and pointer selection move the active visual cell.
API reference
Inputs
CPinInput server inputs
Server inputs are passed in a template through <c-CPinInput ... /> or in Python through CPinInput(...).
| Input | Type | Default | Effect |
|---|---|---|---|
value | str | "" | Sets the initial exact string and preserves leading zeroes. |
name | str | None | None | Sets the native Form field name. |
form | str | None | None | Associates the native input with an external Form ID. |
id | str | None | generated | Sets the native input ID and bases the root ID. |
length | int | 6 | Sets one through thirty-two characters cells maxlength and exact validity. |
type | CPinInputType (CPinInputType) | "numeric" | Chooses the ASCII numeric alphabetic or alphanumeric token alphabet. |
required | bool | None | None | Enables exact-length native required validity outside Field. |
disabled | bool | None | None | Blocks focus edits and Form submission outside Field. |
readonly | bool | None | None | Preserves focus selection and submission while blocking edits outside Field. |
invalid | bool | None | None | Reflects application invalid state outside Field. |
mask | bool | False | Replaces filled visual cells with bullets without changing the value. |
one_time_code | bool | True | Emits one-time-code autocomplete unless input_attrs supplies another token. |
placeholder | one-code-point str | None | "β" | Supplies the caller-authored empty-cell marker. |
attached | bool | False | Joins adjacent visual cells. |
separator_after | Sequence[int] | None | None | Selects zero-based boundaries after which the separator slot renders. |
label | str | None | None | Names a standalone native input; use the Field label slot inside Field. |
size | CPinInputSize (CPinInputSize) | "md" | Selects coordinated cell sizing. |
variant | CPinInputVariant (CPinInputVariant) | "outline" | Selects cell surface treatment. |
class_ | CClassValue | None (CClassValue) | None | Adds classes to the root and merges with attrs. |
style | CStyleValue | None (CStyleValue) | None | Adds styles to the root and merges with attrs. |
attrs | Mapping[str, object] | None | None | Adds copied allowed root attributes without replacing owned state or identity. |
input_attrs | Mapping[str, object] | None | None | Adds copied native attributes including accessible naming descriptions autocomplete and dir without replacing owned behavior. |
CPinInput client inputs
Client inputs are passed in the browser through the $c-props="{ ... }" attribute on <c-CPinInput />.
| Input | Type | Omitted behavior | Effect |
|---|---|---|---|
value | string | Releases control to the last uncontrolled value. | Controls the exact accepted token string. |
required | boolean | Uses server or Field state. | Controls standalone exact-length validity. |
disabled | boolean | Uses server or owner state. | Controls editing focus and Form participation. |
readonly | boolean | Uses server or owner state. | Controls focusable nonmutable submission. |
invalid | boolean | Uses server or Field state. | Controls application invalid state. |
mask | boolean | Uses the server value. | Controls visual masking. |
variant | CPinInputVariant (CPinInputVariant) | Uses the server value. | Controls surface treatment. |
size | CPinInputSize (CPinInputSize) | Uses the server value. | Controls coordinated sizing. |
onValueChange | function | No value callback. | Receives each accepted user edit or reset request. |
onComplete | function | No completion callback. | Receives transitions to a complete accepted token. |
onValueInvalid | function | No rejection callback. | Receives discarded characters and their input source. |
onFocusChange | function | No focus callback. | Receives native focus entry and exit. |
Slots
Slots are passed as nested content or <c-fill> tags in a template, or through the slots={...} argument in Python.
CPinInput slots
| Slot | Required | Data | Fallback |
|---|---|---|---|
separator | no | {index: int} (CPinInputSeparatorSlotData) | No visual content at each separator_after boundary. |
Events
Component events are callback inputs supplied through $c-props. Native browser events remain available through Alpine @... attributes.
CPinInput events
| Event | Signature | Trigger and timing | Detail | Controlled and cancellation behavior |
|---|---|---|---|---|
onValueChange | (value: string, detail: CPinInputValueChangeDetail) => void (CPinInputValueChangeDetail) | An accepted edit or Form reset requests a different value. | {value, previousValue, controlled, source, sourceEvent} (CPinInputValueChangeDetail) | Uncontrolled state commits first; controlled state is request-only. |
onComplete | (value: string, detail: CPinInputCompleteDetail) => void (CPinInputCompleteDetail) | Accepted input transitions to the exact configured length. | {value, controlled, source, sourceEvent} (CPinInputCompleteDetail) | Reports completion without automatically submitting. |
onValueInvalid | (detail: CPinInputInvalidDetail) => void (CPinInputInvalidDetail) | One edit contains disallowed or overflow characters. | {value, rejected, source, sourceEvent} (CPinInputInvalidDetail) | Reports plain rejected text after filtering it from the value. |
onFocusChange | (focused: boolean, detail: CPinInputFocusChangeDetail) => void (CPinInputFocusChangeDetail) | The native text input focuses or blurs. | {focused, sourceEvent} (CPinInputFocusChangeDetail) | Runs after focus reflection changes. |
Methods
-
CSS
CSS variables to theme the components. Set them on an ancestor or the component itself.
CPinInput CSS variables
Apply these variables to CPinInput or one of its ancestors.
| Variable | Type | Purpose | Default |
|---|---|---|---|
--cui-pin-input-cell-size | length | Visual cell inline and block size. | Size-dependent 2.75rem. |
--cui-pin-input-gap | length | Space between separate cells. | 0.5rem |
--cui-pin-input-separator-gap | length | Extra space for a separator boundary. | 0.4rem |
--cui-pin-input-border-color | color | Outline cell border. | Mixed CanvasText. |
--cui-pin-input-focus-color | color | Active-cell focus ring. | Highlight |
--cui-pin-input-invalid-color | color | Invalid border treatment. | Theme danger color. |
--cui-pin-input-background | color | Cell surface. | Canvas |
--cui-pin-input-color | color | Entered character color. | CanvasText |
--cui-pin-input-placeholder-color | color | Empty-cell marker color. | Muted CanvasText. |
--cui-pin-input-radius | length | Cell corner radius. | 0.5rem |
--cui-pin-input-disabled-opacity | number | Disabled treatment opacity. | 0.58 |
Attributes
HTML attributes defined on the components that you can refer to for CSS, inspection, and testing. Read-only.
CPinInput attributes
| Attribute | Element | Type | Meaning |
|---|---|---|---|
data-required | Root div | present | absent | Mirrors effective requiredness. |
data-disabled | Root div | present | absent | Mirrors effective disabledness. |
data-readonly | Root div | present | absent | Mirrors effective readonly state. |
data-invalid | Root div | present | absent | Mirrors effective application or native invalidity. |
data-focused | Root div | present | absent | Marks native input focus. |
data-filled | Root div | present | absent | Marks any accepted character. |
data-complete | Root div | present | absent | Marks an accepted exact-length value. |
data-attached | Root div | present | absent | Marks joined cell styling. |
data-variant | Root div | CPinInputVariant (CPinInputVariant) | Mirrors surface treatment. |
data-size | Root div | CPinInputSize (CPinInputSize) | Mirrors coordinated sizing. |
CPinInput attributes
| Attribute | Element | Type | Meaning |
|---|---|---|---|
data-active | Visual cell | present | absent | Marks the logical native selection or insertion cell. |
data-filled | Visual cell | present | absent | Marks an accepted character. |
data-masked | Visual cell | present | absent | Marks a character currently displayed as a bullet. |
Selectors
Selectors for the DOM nodes in the components that you can use for CSS, inspection, and testing.
CPinInput selectors
| Selector | Element | Purpose |
|---|---|---|
[data-citry-ui-part="pin-input"] | Root div | State reflections and root customization destination. |
[data-citry-ui-part="input"] | Native text input | Owns semantics focus editing validation Form value and input_attrs. |
[data-citry-ui-part="cells"] | Aria-hidden presentation span | Contains the segmented visual display. |
[data-citry-ui-part="cell"] | Visual cell span | Displays one accepted position and receives pointer selection. |
[data-citry-ui-part="character"] | Character span | Displays entered masked or placeholder content. |
[data-citry-ui-part="caret"] | Decorative caret span | Marks an active empty insertion cell. |
[data-citry-ui-part="separator"] | Visual separator span | Hosts the caller separator slot at selected boundaries. |
Interfaces
Aliases and data shapes referenced above.
Input type aliases
| Interface | Definition |
|---|---|
CPinInputType | Literal["numeric", "alphabetic", "alphanumeric"] |
CPinInputSize | Literal["sm", "md", "lg"] |
CPinInputVariant | Literal["outline", "subtle"] |
CPinInputChangeSource | Literal["input", "paste", "autofill", "composition", "reset"] |
CPinInputInvalidSource | Literal["input", "paste", "autofill", "composition"] |
CClassValue | str | Mapping[str, bool] | Sequence[CClassValue] |
CStyleValue | str | Mapping[str, object] | Sequence[CStyleValue] |
CPinInputSeparatorSlotData
| Field | Type | Default | Meaning |
|---|---|---|---|
index | int | - | Zero-based cell index after which this separator renders. |
CPinInputValueChangeDetail
| Field | Type | Default | Meaning |
|---|---|---|---|
value | string | - | Requested accepted token. |
previousValue | string | - | Effective token before the request. |
controlled | boolean | - | Whether client value owns committed state. |
source | CPinInputChangeSource (CPinInputChangeSource) | - | Edit or reset source. |
sourceEvent | object | null | - | Native event when one exists. |
CPinInputCompleteDetail
| Field | Type | Default | Meaning |
|---|---|---|---|
value | string | - | Complete accepted token. |
controlled | boolean | - | Whether client value owns committed state. |
source | CPinInputChangeSource (CPinInputChangeSource) | - | Completion source. |
sourceEvent | object | null | - | Native event. |
CPinInputInvalidDetail
| Field | Type | Default | Meaning |
|---|---|---|---|
value | string | - | Accepted filtered token. |
rejected | string | - | Discarded plain characters. |
source | CPinInputInvalidSource (CPinInputInvalidSource) | - | Rejected edit source. |
sourceEvent | object | null | - | Native input event. |
CPinInputFocusChangeDetail
| Field | Type | Default | Meaning |
|---|---|---|---|
focused | boolean | - | Current native focus state. |
sourceEvent | object | - | Native focus or blur event. |
Translation keys
-