<tui-textfield>
<input
tuiPincode
[(ngModel)]="value"
/>
</tui-textfield>
type="password" — entered digits render as filled dots
<tui-textfield>
<input
tuiPincode
type="password"
[maxLength]="6"
[(ngModel)]="value"
/>
</tui-textfield>
1234 for success, any other code for error
@if (done()) {
<div class="done">PIN verified ✓</div>
} @else {
<tui-textfield>
<input
tuiPincode
[invalid]="verification.value()"
[(ngModel)]="pin"
(confirmed)="done.set(true)"
/>
</tui-textfield>
}
:host {
display: flex;
flex-direction: column;
align-items: center;
gap: 1.5rem;
padding: 1.5rem;
}
1234 for success, any other code for error
@if (done()) {
<div class="done">PIN verified ✓</div>
} @else {
<div
class="field"
[class.field_success]="verification.value() === false"
>
<tui-loader
class="loader"
[inheritColor]="true"
[loading]="verification.value() === false"
/>
<tui-textfield>
<input
tuiPincode
[invalid]="verification.value()"
[(ngModel)]="pin"
(finished)="onFinished()"
/>
</tui-textfield>
</div>
}
:host {
display: flex;
flex-direction: column;
align-items: center;
gap: 1.5rem;
padding: 1.5rem;
}
.done {
color: var(--tui-text-positive);
font-size: 1rem;
font-weight: 500;
}
.field {
position: relative;
display: flex;
align-items: center;
justify-content: center;
block-size: 4rem;
}
tui-loader {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
color: var(--tui-text-action);
scale: 0;
pointer-events: none;
}
.field_success tui-loader {
animation: loaderIn 500ms cubic-bezier(0, 0, 0.58, 1) 2150ms forwards;
}
@keyframes loaderIn {
0% {
scale: 0;
}
30% {
scale: 1.2;
}
100% {
scale: 1;
}
}
1234 for successful validation
<button
tuiButton
type="button"
(click)="open.set(true)"
>
Open dialog
</button>
<ng-template
let-observer
[tuiDialogOptions]="{size: 's'}"
[(tuiDialog)]="open"
>
<div class="content">
<tui-thumbnail-card
paymentSystem="mir"
size="m"
>
2206
</tui-thumbnail-card>
<header
tuiHeader="h5"
class="heading"
>
<h2 tuiTitle>Enter PIN</h2>
</header>
<div class="container">
<tui-textfield>
<input
tuiAutoFocus
tuiPincode
type="password"
[invalid]="verification.value()"
[(ngModel)]="pin"
(confirmed)="open.set(false); pin.set('')"
/>
</tui-textfield>
</div>
</div>
<footer class="footer">
<button
appearance="flat"
size="m"
tuiButton
type="button"
(click)="observer.complete()"
>
Cancel
</button>
</footer>
</ng-template>
.content {
display: flex;
flex-direction: column;
align-items: center;
gap: 1.25rem;
padding: 1.5rem 1.5rem 0;
}
.heading {
justify-content: center;
}
.container {
display: flex;
align-items: center;
justify-content: center;
block-size: 4rem;
}
.footer {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 0.5rem;
padding-block-start: 0.5rem;
}
inputmode="none" suppresses the native keyboard. Enter 1234 for successful validation; wrong code triggers shake-clear-retry
<button
tuiButton
type="button"
(click)="open.set(true)"
>
Open sheet
</button>
<ng-template
let-observer
[tuiSheetDialogOptions]="options"
[(tuiSheetDialog)]="open"
>
<header class="header">
<tui-app-bar>
<button
tuiButton
tuiSlot="start"
type="button"
(click)="observer.complete()"
>
Close
</button>
</tui-app-bar>
</header>
<div class="content">
<tui-thumbnail-card
paymentSystem="mir"
size="m"
>
2206
</tui-thumbnail-card>
<header tuiHeader="h5">
<h2 tuiTitle>Create a PIN</h2>
</header>
<div class="container">
<tui-textfield>
<input
inputmode="none"
tuiPincode
type="password"
[invalid]="verification.value()"
[(ngModel)]="pin"
(confirmed)="open.set(false); pin.set('')"
/>
</tui-textfield>
</div>
<div class="pad">
@for (key of keys; track key) {
@if (key === '') {
<span></span>
} @else if (key === 'backspace') {
<button
type="button"
class="key"
[disabled]="verification.value() != null"
(click)="onKey(key)"
>
<tui-icon icon="@tui.delete" />
</button>
} @else {
<button
type="button"
class="key"
[disabled]="verification.value() != null"
(click)="onKey(key)"
>
{{ key }}
</button>
}
}
</div>
<button
tuiLink
type="button"
(click)="observer.complete()"
>
Skip
</button>
</div>
</ng-template>
.header {
margin-block-end: 1.5rem;
}
.content {
display: flex;
flex-direction: column;
align-items: center;
flex-grow: 1;
}
.container {
display: flex;
align-items: center;
justify-content: center;
block-size: 4rem;
margin-block-end: auto;
}
.pad {
display: grid;
grid-template-columns: repeat(3, 4.5rem);
gap: 0.5rem 1.5rem;
justify-items: center;
margin-block-end: 0.5rem;
}
.key {
display: flex;
align-items: center;
justify-content: center;
inline-size: 4.5rem;
block-size: 4.5rem;
border: none;
border-radius: 50%;
background: transparent;
font: 2.5rem/1 var(--tui-typography-family-display);
color: var(--tui-text-primary);
cursor: pointer;
transition: background var(--tui-duration-short, 150ms);
@media (pointer: fine) {
&:hover {
background: var(--tui-background-neutral-1-hover);
}
}
&:focus-visible {
outline: 0.125rem solid var(--tui-border-focus);
outline-offset: 0.125rem;
}
&:active {
background: var(--tui-background-neutral-1-pressed);
}
&:disabled {
opacity: 0.4;
cursor: default;
}
}
tui-app-bar {
align-self: stretch;
}