A highly customizable React component library for code input fields. Perfect for verification codes, OTP inputs, PIN entries, and more. Supports two distinct design patterns: individual boxes and single line inputs.
- β Two Design Variants: Box-style (individual inputs) and Line-style (single input with spacing)
- β Full TypeScript Support: Complete type safety and IntelliSense
- β Highly Customizable: Extensive styling options for borders, colors, fonts, and spacing
- β Separator Support: Add custom separators at specified positions
- β Keyboard Navigation: Arrow keys, backspace, and automatic focus management
- β Paste Support: Smart paste handling with automatic formatting
- β Ref Methods: Programmatic control with getValue, setValue, clear, and focus
- β Responsive Design: Mobile-friendly with responsive breakpoints
- β Accessibility: ARIA-compliant and keyboard accessible
- β Zero Dependencies: Lightweight with no external dependencies
npm install react-segmented-input
Don't forget import package styles!
import "react-segmented-input/style.css";
import React, { useRef } from "react";
import { BoxCodeInput } from "react-segmented-input";
import "react-segmented-input/style.css";
function App() {
const inputRef = useRef(null);
const handleComplete = (value) => {
console.log("Code entered:", value);
};
const getValue = () => {
const value = inputRef.current?.getValue();
console.log("Current value:", value);
};
return (
<div>
<BoxCodeInput
ref={inputRef}
numberOfChars={6}
separatorPositions={[3]}
separatorChar="-"
gap={12}
onChange={(value) => console.log("Current value:", value)}
onComplete={handleComplete}
autoFocus
/>
<button onClick={getValue}>Get Value</button>
</div>
);
}
export default App;
import React, { useRef } from "react";
import { LineCodeInput } from "react-segmented-input";
import "react-segmented-input/style.css";
function App() {
const inputRef = useRef(null);
const handleComplete = (value) => {
console.log("Code entered:", value);
};
return (
<div>
<LineCodeInput
ref={inputRef}
numberOfChars={8}
separatorPositions={[4]}
separatorChar="/"
letterSpacing={12}
onChange={(value) => console.log("Current value:", value)}
onComplete={handleComplete}
/>
</div>
);
}
export default App;
import React, { useRef } from "react";
import { BoxCodeInput, CodeInputRef } from "react-segmented-input";
import "react-segmented-input/style.css";
function App(): JSX.Element {
const inputRef = useRef<CodeInputRef>(null);
const handleComplete = (value: string): void => {
console.log("Code entered:", value);
};
const getValue = (): void => {
const value = inputRef.current?.getValue();
console.log("Current value:", value);
};
return (
<div>
<BoxCodeInput
ref={inputRef}
numberOfChars={6}
separatorPositions={[3]}
separatorChar="-"
gap={12}
onChange={(value: string) => console.log("Current value:", value)}
onComplete={handleComplete}
autoFocus
/>
<button onClick={getValue}>Get Value</button>
</div>
);
}
export default App;
import React, { useRef } from "react";
import { LineCodeInput, CodeInputRef } from "react-segmented-input";
import "react-segmented-input/style.css";
function App(): JSX.Element {
const inputRef = useRef<CodeInputRef>(null);
const handleComplete = (value: string): void => {
console.log("Code entered:", value);
};
return (
<div>
<LineCodeInput
ref={inputRef}
numberOfChars={8}
separatorPositions={[4]}
separatorChar="/"
letterSpacing={12}
onChange={(value: string) => console.log("Current value:", value)}
onComplete={handleComplete}
/>
</div>
);
}
export default App;
Prop | Type | Default | Description |
---|---|---|---|
numberOfChars |
number |
required | Number of characters/boxes |
borderTop |
boolean |
true |
Show top border |
borderRight |
boolean |
true |
Show right border |
borderBottom |
boolean |
true |
Show bottom border |
borderLeft |
boolean |
true |
Show left border |
border |
boolean |
true |
Show all borders (overrides individual border props) |
borderThickness |
number |
1 |
Border thickness in pixels |
borderColor |
string |
'#ccc' |
Border color (hex, rgb, or named colors) |
backgroundColor |
string |
'transparent' |
Background color |
fontSize |
number |
16 |
Font size in pixels |
fontWeight |
number |
400 |
Font weight (100-900) |
textColor |
string |
'#000' |
Text color |
borderRadius |
number |
4 |
Border radius in pixels |
width |
number |
40 |
Width of each box in pixels |
height |
number |
40 |
Height of each box in pixels |
gap |
number |
8 |
Space between boxes in pixels |
separatorPositions |
number[] |
[] |
Positions where separators should appear |
separatorChar |
string |
'-' |
Character to use as separator |
onChange |
(value: string) => void |
- | Called when value changes |
onComplete |
(value: string) => void |
- | Called when all characters are entered |
value |
string |
'' |
Controlled value |
placeholder |
string |
'' |
Placeholder text for each box |
disabled |
boolean |
false |
Disable the input |
autoFocus |
boolean |
false |
Auto focus first input on mount |
className |
string |
'' |
Additional CSS class |
style |
React.CSSProperties |
{} |
Additional inline styles |
Prop | Type | Default | Description |
---|---|---|---|
numberOfChars |
number |
required | Number of characters |
borderTop |
boolean |
true |
Show top border |
borderRight |
boolean |
true |
Show right border |
borderBottom |
boolean |
true |
Show bottom border |
borderLeft |
boolean |
true |
Show left border |
border |
boolean |
true |
Show all borders |
borderThickness |
number |
1 |
Border thickness in pixels |
borderColor |
string |
'#ccc' |
Border color |
backgroundColor |
string |
'transparent' |
Background color |
fontSize |
number |
16 |
Font size in pixels |
fontWeight |
number |
400 |
Font weight |
textColor |
string |
'#000' |
Text color |
textAlign |
'center' | 'left' | 'right' |
'center' |
Text alignment |
letterSpacing |
number |
8 |
Space between characters in pixels |
borderRadius |
number |
4 |
Border radius in pixels |
paddingTop |
number |
12 |
Top padding in pixels |
paddingRight |
number |
16 |
Right padding in pixels |
paddingBottom |
number |
12 |
Bottom padding in pixels |
paddingLeft |
number |
16 |
Left padding in pixels |
width |
number |
- | Fixed width (auto-calculated if not provided) |
separatorPositions |
number[] |
[] |
Positions for separators |
separatorChar |
string |
'-' |
Separator character |
onChange |
(value: string) => void |
- | Value change callback |
onComplete |
(value: string) => void |
- | Completion callback |
value |
string |
'' |
Controlled value |
placeholder |
string |
'' |
Placeholder text |
disabled |
boolean |
false |
Disable input |
autoFocus |
boolean |
false |
Auto focus on mount |
className |
string |
'' |
CSS class |
style |
React.CSSProperties |
{} |
Inline styles |
Both components support ref methods for programmatic control:
Method | Description |
---|---|
getValue() |
Returns the current input value |
setValue(value: string) |
Sets the input value |
clear() |
Clears the input and focuses first field |
focus() |
Focuses the appropriate input field |
import React, { useRef } from "react";
import { BoxCodeInput } from "react-segmented-input";
import "react-segmented-input/style.css";
function App() {
const inputRef = useRef(null);
const handleGetValue = () => {
const value = inputRef.current?.getValue();
alert(`Current value: ${value}`);
};
const handleClear = () => {
inputRef.current?.clear();
};
const handleSetValue = () => {
inputRef.current?.setValue("123456");
};
return (
<div>
<BoxCodeInput
ref={inputRef}
numberOfChars={6}
borderColor="#3b82f6"
backgroundColor="white"
/>
<div>
<button onClick={handleGetValue}>Get Value</button>
<button onClick={handleClear}>Clear</button>
<button onClick={handleSetValue}>Set Value</button>
</div>
</div>
);
}
export default App;
import React, { useRef } from "react";
import { BoxCodeInput, CodeInputRef } from "react-segmented-input";
import "react-segmented-input/style.css";
function App(): JSX.Element {
const inputRef = useRef<CodeInputRef>(null);
const handleGetValue = (): void => {
const value = inputRef.current?.getValue();
alert(`Current value: ${value}`);
};
const handleClear = (): void => {
inputRef.current?.clear();
};
const handleSetValue = (): void => {
inputRef.current?.setValue("123456");
};
return (
<div>
<BoxCodeInput
ref={inputRef}
numberOfChars={6}
borderColor="#3b82f6"
backgroundColor="white"
/>
<div>
<button onClick={handleGetValue}>Get Value</button>
<button onClick={handleClear}>Clear</button>
<button onClick={handleSetValue}>Set Value</button>
</div>
</div>
);
}
export default App;
<BoxCodeInput
numberOfChars={11}
separatorPositions={[1, 4, 7]}
separatorChar=" "
borderColor="#6b7280"
textColor="#374151"
placeholder="0"
width={35}
height={40}
/>
<LineCodeInput
numberOfChars={16}
separatorPositions={[4, 8, 12]}
separatorChar=" "
borderColor="#f59e0b"
textColor="#92400e"
letterSpacing={8}
textAlign="center"
placeholder="0000 0000 0000 0000"
/>
<LineCodeInput
numberOfChars={26}
separatorPositions={[2, 6, 10, 14, 18, 22]}
separatorChar=" "
borderColor="#8b5cf6"
textColor="#6d28d9"
fontSize={14}
letterSpacing={4}
textAlign="left"
width={400}
placeholder="TR00 0000 0000 0000 0000 0000 00"
/>
<BoxCodeInput
numberOfChars={6}
borderColor="#10b981"
backgroundColor="#f0fdf4"
textColor="#065f46"
fontSize={24}
fontWeight={600}
borderRadius={8}
width={50}
height={50}
autoFocus
onComplete={(code) => {
// Verify OTP
verifyOTP(code);
}}
/>
<LineCodeInput
numberOfChars={8}
separatorPositions={[4]}
separatorChar="-"
borderColor="#ec4899"
backgroundColor="#fdf2f8"
textColor="#be185d"
fontSize={20}
fontWeight={500}
letterSpacing={15}
borderRadius={12}
paddingTop={20}
paddingBottom={20}
paddingLeft={25}
paddingRight={25}
style={{
boxShadow: "0 4px 6px -1px rgba(0, 0, 0, 0.1)",
}}
/>
Separators are added after the specified character positions:
// For numberOfChars={8} and separatorPositions={[2, 6]}
// Input: "12345678"
// Display: "12-345678" (separator after 2nd position)
// "12-3456-78" (separator after 6th position)
<LineCodeInput
numberOfChars={8}
separatorPositions={[2, 6]}
separatorChar="-"
/>
// Uncontrolled (recommended for most cases)
<BoxCodeInput numberOfChars={6} onChange={(value) => console.log(value)} />;
// Controlled
function ControlledExample() {
const [value, setValue] = useState("");
return <BoxCodeInput numberOfChars={6} value={value} onChange={setValue} />;
}
function ValidatedInput() {
const [value, setValue] = useState("");
const [isValid, setIsValid] = useState(true);
const handleChange = (newValue) => {
setValue(newValue);
// Custom validation logic
setIsValid(newValue.length === 0 || /^\d+$/.test(newValue));
};
return (
<BoxCodeInput
numberOfChars={6}
value={value}
onChange={handleChange}
borderColor={isValid ? "#10b981" : "#ef4444"}
backgroundColor={isValid ? "#f0fdf4" : "#fef2f2"}
/>
);
}
The components provide CSS classes for custom styling:
/* BoxCodeInput */
.box-code-input {
/* Container styles */
}
.box-code-input__field {
/* Individual input field styles */
}
.box-code-input__separator {
/* Separator styles */
}
/* LineCodeInput */
.line-code-input {
/* Container styles */
}
.line-code-input__field {
/* Input field styles */
}
.my-custom-input .box-code-input__field {
border: 2px solid #3b82f6;
border-radius: 8px;
font-family: "Monaco", "Menlo", monospace;
}
.my-custom-input .box-code-input__field:focus {
border-color: #1d4ed8;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
The components include responsive breakpoints:
@media (max-width: 640px) {
.box-code-input {
gap: 4px; /* Reduced gap on mobile */
}
.line-code-input__field {
font-size: 14px !important;
letter-spacing: 4px !important;
padding: 8px 12px !important;
}
}
- Lightweight: ~4KB gzipped
- Zero Dependencies: No external dependencies
- Optimized Rendering: Minimal re-renders with React best practices
- Memory Efficient: Proper cleanup and ref management
import { render, fireEvent, screen } from "@testing-library/react";
import { BoxCodeInput } from "react-segmented-input";
test("handles input correctly", () => {
const handleChange = jest.fn();
render(
<BoxCodeInput
numberOfChars={4}
onChange={handleChange}
data-testid="code-input"
/>
);
const inputs = screen.getAllByRole("textbox");
fireEvent.change(inputs[0], { target: { value: "1" } });
expect(handleChange).toHaveBeenCalledWith("1");
});
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature
) - Commit your changes (
git commit -m 'Add amazing feature'
) - Push to the branch (
git push origin feature/amazing-feature
) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
If you find this package helpful, please consider:
- β Starring the repository
- π Reporting bugs
- π‘ Suggesting new features
- π Improving documentation
- Chrome β₯ 60
- Firefox β₯ 60
- Safari β₯ 12
- Edge β₯ 79