diff --git a/ui/content/docs/components/input.mdx b/ui/content/docs/components/input.mdx
new file mode 100644
index 0000000..a6a99e8
--- /dev/null
+++ b/ui/content/docs/components/input.mdx
@@ -0,0 +1,188 @@
+---
+title: Input
+description: A text input component
+---
+
+import { Input } from '@trakteer/input';
+import { TypeTable } from 'fumadocs-ui/components/type-table';
+
+
+
+
+
+### Usage
+
+```bash tab="npm"
+npx @fydemy/ui@latest add input
+```
+
+```bash tab="pnpm"
+pnpm dlx @fydemy/ui@latest add input
+```
+
+```bash tab="yarn"
+yarn @fydemy/ui@latest add input
+```
+
+```bash tab="bun"
+bunx --bun @fydemy/ui@latest add input
+```
+
+```jsx
+
+```
+
+) => void;
+ onFocus?: (e: React.FocusEvent) => void;
+ onBlur?: (e: React.FocusEvent) => void;
+};
+`}
+/>
+
+### Examples
+
+import { Mail, Search, Lock, User } from 'lucide-react';
+
+#### With Label
+
+An input with a label for better accessibility and user experience.
+
+
+
+
+
+
+```jsx
+
+
+```
+
+#### With Icon
+
+An input with icon on the left or right side. Use `lucide-react` icon is recommended.
+
+
+ } iconPosition="left" />
+ } iconPosition="right" />
+
+
+```jsx
+}
+ iconPosition="left"
+/>
+}
+ iconPosition="right"
+/>
+```
+
+#### Variants
+
+Control the variant either `default`, `error`, or `success`. `default` is the default value.
+
+
+
+
+
+
+
+```jsx
+
+
+
+```
+
+#### With Helper Text
+
+Add helper text or error message to provide additional context or validation feedback.
+
+
+
+
+
+
+```jsx
+
+
+```
+
+#### States
+
+Input supports `disabled` and `readonly` states.
+
+
+
+
+
+
+```jsx
+
+
+```
+
+#### Input Types
+
+Support for various HTML input types.
+
+
+
+ } iconPosition="left" />
+ } iconPosition="left" />
+
+ } iconPosition="left" />
+
+
+```jsx
+
+} iconPosition="left" />
+} iconPosition="left" />
+
+} iconPosition="left" />
+```
+
+#### Controlled Input
+
+Use `value` and `onChange` to control the input state.
+
+```jsx
+const [value, setValue] = useState('');
+
+ setValue(e.target.value)} placeholder="Type something..." />;
+```
diff --git a/ui/public/trakteer/input/index.css b/ui/public/trakteer/input/index.css
new file mode 100644
index 0000000..7793daf
--- /dev/null
+++ b/ui/public/trakteer/input/index.css
@@ -0,0 +1,157 @@
+/* Input Wrapper */
+.input-wrapper[data-theme='trakteer'] {
+ font-family: 'Pontano Sans', sans-serif;
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+ width: 100%;
+}
+
+/* Input Label */
+.input-wrapper[data-theme='trakteer'] .input-label {
+ font-size: 14px;
+ font-weight: 600;
+ color: var(--trakteer-text-primary);
+ margin-bottom: 0.25rem;
+}
+
+/* Input Container */
+.input-wrapper[data-theme='trakteer'] .input-container {
+ position: relative;
+ display: flex;
+ align-items: center;
+ width: 100%;
+}
+
+/* Input Base Styles */
+.input-wrapper[data-theme='trakteer'] .input {
+ font-family: 'Pontano Sans', sans-serif;
+ width: 100%;
+ padding: 0.75rem 1rem;
+ font-size: 16px;
+ color: var(--trakteer-text-primary);
+ background-color: var(--trakteer-bg-primary);
+ border: 1px solid var(--trakteer-border-medium);
+ border-bottom: 4px solid var(--trakteer-border-medium);
+ border-radius: 12px;
+ outline: none;
+ transition: all 0.2s ease;
+}
+
+.input-wrapper[data-theme='trakteer'] .input::placeholder {
+ color: var(--trakteer-text-muted);
+}
+
+/* Input with Icon */
+.input-wrapper[data-theme='trakteer'] .input-container:has(.input-icon-left) .input {
+ padding-left: 2.75rem;
+}
+
+.input-wrapper[data-theme='trakteer'] .input-container:has(.input-icon-right) .input {
+ padding-right: 2.75rem;
+}
+
+/* Input Icon */
+.input-wrapper[data-theme='trakteer'] .input-icon {
+ position: absolute;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: var(--trakteer-text-secondary);
+ z-index: 1;
+}
+
+.input-wrapper[data-theme='trakteer'] .input-icon-left {
+ left: 1rem;
+}
+
+.input-wrapper[data-theme='trakteer'] .input-icon-right {
+ right: 1rem;
+}
+
+.input-wrapper[data-theme='trakteer'] .input-icon svg {
+ width: 18px;
+ height: 18px;
+}
+
+/* Input Default Variant */
+.input-wrapper[data-theme='trakteer'] .input[data-variant='default'] {
+ border-color: var(--trakteer-border-medium);
+ border-bottom-color: var(--trakteer-border-medium);
+}
+
+.input-wrapper[data-theme='trakteer'] .input[data-variant='default']:focus {
+ border-color: var(--trakteer-default-border);
+ border-bottom-color: var(--trakteer-default-border);
+ background-color: var(--trakteer-bg-primary);
+}
+
+.input-wrapper[data-theme='trakteer'] .input[data-variant='default']:focus + .input-icon-right,
+.input-wrapper[data-theme='trakteer'] .input-container:has(.input[data-variant='default']:focus) .input-icon {
+ color: var(--trakteer-default);
+}
+
+/* Input Error Variant */
+.input-wrapper[data-theme='trakteer'] .input[data-variant='error'] {
+ border-color: var(--trakteer-destructive-border);
+ border-bottom-color: var(--trakteer-destructive-border);
+ background-color: var(--trakteer-bg-primary);
+}
+
+.input-wrapper[data-theme='trakteer'] .input[data-variant='error']:focus {
+ border-color: var(--trakteer-destructive-border);
+ border-bottom-color: var(--trakteer-destructive-border);
+}
+
+.input-wrapper[data-theme='trakteer'] .input[data-variant='error']:focus + .input-icon-right,
+.input-wrapper[data-theme='trakteer'] .input-container:has(.input[data-variant='error']:focus) .input-icon {
+ color: var(--trakteer-destructive);
+}
+
+/* Input Success Variant */
+.input-wrapper[data-theme='trakteer'] .input[data-variant='success'] {
+ border-color: var(--trakteer-default-border);
+ border-bottom-color: var(--trakteer-default-border);
+ background-color: var(--trakteer-bg-primary);
+}
+
+.input-wrapper[data-theme='trakteer'] .input[data-variant='success']:focus {
+ border-color: var(--trakteer-default-border);
+ border-bottom-color: var(--trakteer-default-border);
+}
+
+.input-wrapper[data-theme='trakteer'] .input[data-variant='success']:focus + .input-icon-right,
+.input-wrapper[data-theme='trakteer'] .input-container:has(.input[data-variant='success']:focus) .input-icon {
+ color: var(--trakteer-default);
+}
+
+/* Input Disabled State */
+.input-wrapper[data-theme='trakteer'] .input:disabled {
+ background-color: var(--trakteer-bg-secondary);
+ border-color: var(--trakteer-border-light);
+ border-bottom-color: var(--trakteer-border-light);
+ color: var(--trakteer-text-muted);
+ cursor: not-allowed;
+ opacity: 0.6;
+}
+
+.input-wrapper[data-theme='trakteer'] .input:disabled::placeholder {
+ color: var(--trakteer-text-muted);
+}
+
+/* Input Readonly State */
+.input-wrapper[data-theme='trakteer'] .input[readonly] {
+ background-color: var(--trakteer-bg-secondary);
+ cursor: default;
+}
+
+/* Input Helper Text */
+.input-wrapper[data-theme='trakteer'] .input-helper {
+ font-size: 12px;
+ color: var(--trakteer-text-secondary);
+ margin-top: 0.25rem;
+}
+
+.input-wrapper[data-theme='trakteer'] .input-helper-error {
+ color: var(--trakteer-destructive);
+}
diff --git a/ui/public/trakteer/input/index.tsx b/ui/public/trakteer/input/index.tsx
new file mode 100644
index 0000000..ce1c3dd
--- /dev/null
+++ b/ui/public/trakteer/input/index.tsx
@@ -0,0 +1,71 @@
+import '../index.css';
+import './index.css';
+
+import { forwardRef } from 'react';
+
+type InputProps = {
+ type?: 'text' | 'email' | 'password' | 'number' | 'tel' | 'url' | 'search';
+ placeholder?: string;
+ value?: string;
+ defaultValue?: string;
+ variant?: 'default' | 'error' | 'success';
+ disabled?: boolean;
+ readonly?: boolean;
+ label?: string;
+ helperText?: string;
+ errorMessage?: string;
+ icon?: React.ReactNode;
+ iconPosition?: 'left' | 'right';
+ className?: string;
+ onChange?: (e: React.ChangeEvent) => void;
+ onFocus?: (e: React.FocusEvent) => void;
+ onBlur?: (e: React.FocusEvent) => void;
+} & React.InputHTMLAttributes;
+
+const Input = forwardRef(
+ ({ type = 'text', placeholder, value, defaultValue, variant = 'default', disabled = false, readonly = false, label, helperText, errorMessage, icon, iconPosition = 'left', className = '', onChange, onFocus, onBlur, ...props }, ref) => {
+ const hasError = variant === 'error' || errorMessage;
+ const hasSuccess = variant === 'success';
+ const displayVariant = hasError ? 'error' : hasSuccess ? 'success' : 'default';
+
+ return (
+
+ {label && (
+
+ )}
+
+ {icon && iconPosition === 'left' && {icon}}
+
+ {icon && iconPosition === 'right' && {icon}}
+
+ {(errorMessage || helperText) && (
+
+ {errorMessage || helperText}
+
+ )}
+
+ );
+ }
+);
+
+Input.displayName = 'Input';
+
+export { Input };