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