Componentinput-otp
Input OTP
Accessible one-time password component with copy paste functionality.
Preview
Accessible one-time password component with copy paste functionality.
1
2
3
@ui.InputOTP(ui.InputOTPProps{MaxLength: 6}) {
@ui.InputOTPGroup(ui.DOMProps{}) {
@ui.InputOTPSlot(ui.InputOTPSlotProps{Index: 0})
@ui.InputOTPSlot(ui.InputOTPSlotProps{Index: 1})
@ui.InputOTPSlot(ui.InputOTPSlotProps{Index: 2})
}
@ui.InputOTPSeparator(ui.DOMProps{})
@ui.InputOTPGroup(ui.DOMProps{}) {
@ui.InputOTPSlot(ui.InputOTPSlotProps{Index: 3})
@ui.InputOTPSlot(ui.InputOTPSlotProps{Index: 4})
@ui.InputOTPSlot(ui.InputOTPSlotProps{Index: 5})
}
}Installation
Add this component to your project using the CLI.
templcn add input-otpUsage
1. Import the component in your Go or templ file:
import "your-module/ui"2. Use the component in your template:
@ui.InputOTP(ui.InputOTPProps{MaxLength: 6}) {
@ui.InputOTPGroup(ui.DOMProps{}) {
@ui.InputOTPSlot(ui.InputOTPSlotProps{Index: 0})
@ui.InputOTPSlot(ui.InputOTPSlotProps{Index: 1})
@ui.InputOTPSlot(ui.InputOTPSlotProps{Index: 2})
}
@ui.InputOTPSeparator(ui.DOMProps{})
@ui.InputOTPGroup(ui.DOMProps{}) {
@ui.InputOTPSlot(ui.InputOTPSlotProps{Index: 3})
@ui.InputOTPSlot(ui.InputOTPSlotProps{Index: 4})
@ui.InputOTPSlot(ui.InputOTPSlotProps{Index: 5})
}
}Composition
Use these parts together to build the component.
InputOTP
├── InputOTPGroup
├── InputOTPSlot
├── InputOTPSeparatorExamples
Named examples and common variations.
6-digit OTP
A 6-slot OTP input split into two groups.
1
2
3
@ui.InputOTP(ui.InputOTPProps{MaxLength: 6}) {
@ui.InputOTPGroup(ui.DOMProps{}) {
@ui.InputOTPSlot(ui.InputOTPSlotProps{Index: 0, Value: "1"})
@ui.InputOTPSlot(ui.InputOTPSlotProps{Index: 1, Value: "2"})
@ui.InputOTPSlot(ui.InputOTPSlotProps{Index: 2, Value: "3"})
}
@ui.InputOTPSeparator(ui.DOMProps{})
@ui.InputOTPGroup(ui.DOMProps{}) {
@ui.InputOTPSlot(ui.InputOTPSlotProps{Index: 3})
@ui.InputOTPSlot(ui.InputOTPSlotProps{Index: 4})
@ui.InputOTPSlot(ui.InputOTPSlotProps{Index: 5})
}
}API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
| MaxLength | int | 0 | Total number of OTP digits. |
| Pattern | string | "" | Regex pattern for allowed characters. |
| Disabled | bool | false | Disables all slots. |
View source
package ui
import (
"context"
"io"
"strconv"
"github.com/a-h/templ"
)
type InputOTPProps struct {
DOMProps
Name string
Value string
DefaultValue string
MaxLength int
Pattern string
Disabled bool
}
func InputOTP(props InputOTPProps) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, w io.Writer) error {
ctx, ownChildren := childrenFromContext(ctx)
value := props.Value
if value == "" {
value = props.DefaultValue
}
attrs := attrsFromDOMProps(props.DOMProps, "input-otp", "flex items-center gap-2 has-disabled:opacity-50 disabled:cursor-not-allowed")
if props.Name != "" {
attrs["data-name"] = props.Name
}
if value != "" {
attrs["data-value"] = value
}
if props.DefaultValue != "" {
attrs["data-default-value"] = props.DefaultValue
}
if props.MaxLength > 0 {
attrs["data-maxlength"] = strconv.Itoa(props.MaxLength)
}
if props.Pattern != "" {
attrs["data-pattern"] = props.Pattern
}
if props.Disabled {
attrs["data-disabled"] = "true"
}
children := templ.ComponentFunc(func(ctx context.Context, w io.Writer) error {
if props.Name != "" {
inputAttrs := templ.Attributes{"type": "hidden", "name": props.Name, "value": value}
if err := renderVoidElement(ctx, w, "input", inputAttrs); err != nil {
return err
}
}
return renderChildren(ctx, w, ownChildren)
})
return renderElement(ctx, w, "div", attrs, children)
})
}
func InputOTPGroup(props DOMProps) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, w io.Writer) error {
return renderElement(ctx, w, "div", attrsFromDOMProps(props, "input-otp-group", "flex items-center"), templ.GetChildren(ctx))
})
}
type InputOTPSlotProps struct {
DOMProps
Index int
Value string
}
func InputOTPSlot(props InputOTPSlotProps) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, w io.Writer) error {
attrs := attrsFromDOMProps(props.DOMProps, "input-otp-slot", "relative flex h-9 w-9 items-center justify-center border-y border-r border-input text-sm shadow-xs transition-all outline-none first:rounded-l-md first:border-l last:rounded-r-md aria-invalid:border-destructive data-[active=true]:z-10 data-[active=true]:border-ring data-[active=true]:ring-[3px] data-[active=true]:ring-ring/50 data-[active=true]:aria-invalid:border-destructive data-[active=true]:aria-invalid:ring-destructive/20 dark:bg-input/30 dark:data-[active=true]:aria-invalid:ring-destructive/40")
if props.Index >= 0 {
attrs["data-index"] = strconv.Itoa(props.Index)
}
attrs["tabindex"] = "0"
attrs["role"] = "textbox"
attrs["aria-label"] = "Digit " + strconv.Itoa(props.Index+1)
return renderTextElement(ctx, w, "div", attrs, props.Value)
})
}
func InputOTPSeparator(props DOMProps) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, w io.Writer) error {
attrs := attrsFromDOMProps(props, "input-otp-separator", "")
attrs["role"] = "separator"
children := templ.GetChildren(ctx)
if children == nil {
children = templ.ComponentFunc(func(ctx context.Context, w io.Writer) error {
_, err := io.WriteString(w, `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14"></path></svg>`)
return err
})
}
return renderElement(ctx, w, "div", attrs, children)
})
}