diff --git a/src/app/shared/ui/input/input.component.html b/src/app/shared/ui/input/input.component.html
new file mode 100644
index 0000000..78fc576
--- /dev/null
+++ b/src/app/shared/ui/input/input.component.html
@@ -0,0 +1,16 @@
+
diff --git a/src/app/shared/ui/input/input.component.scss b/src/app/shared/ui/input/input.component.scss
new file mode 100644
index 0000000..5460d3d
--- /dev/null
+++ b/src/app/shared/ui/input/input.component.scss
@@ -0,0 +1,81 @@
+:host {
+ display: inline-block;
+}
+
+:host.app-input-host--full-width {
+ display: block;
+ width: 100%;
+}
+
+.app-input {
+ width: 100%;
+ border: 1px solid var(--border-color, #e4e4e7);
+ border-radius: var(--radius-md, 6px);
+ background: var(--bg-primary, #fff);
+ color: var(--text-primary, #1f322d);
+ font-family: inherit;
+ line-height: 1;
+ transition: border-color var(--transition-fast, 120ms ease),
+ box-shadow var(--transition-fast, 120ms ease);
+
+ &::placeholder {
+ color: var(--text-secondary, #6b7280);
+ }
+
+ &:hover:not(:disabled) {
+ border-color: var(--primary-color, #2f6e5d);
+ }
+
+ &:focus-visible {
+ outline: none;
+ border-color: var(--primary-color, #2f6e5d);
+ box-shadow: 0 0 0 2px color-mix(in srgb, var(--primary-color, #2f6e5d) 25%, transparent);
+ }
+
+ &:disabled {
+ cursor: not-allowed;
+ opacity: 0.6;
+ }
+}
+
+.app-input--sm {
+ height: 32px;
+ padding: 0 var(--space-sm, 0.5rem);
+ font-size: 0.8125rem;
+}
+
+.app-input--md {
+ height: 40px;
+ padding: 0 var(--space-md, 1rem);
+ font-size: 0.9375rem;
+}
+
+.app-input--lg {
+ height: 48px;
+ padding: 0 var(--space-lg, 1.5rem);
+ font-size: 1rem;
+}
+
+.app-input--error {
+ border-color: var(--error-color, #c0392b);
+
+ &:focus-visible {
+ border-color: var(--error-color, #c0392b);
+ box-shadow: 0 0 0 2px color-mix(in srgb, var(--error-color, #c0392b) 25%, transparent);
+ }
+}
+
+.app-input--success {
+ border-color: var(--success-color, #2e7d32);
+
+ &:focus-visible {
+ border-color: var(--success-color, #2e7d32);
+ box-shadow: 0 0 0 2px color-mix(in srgb, var(--success-color, #2e7d32) 25%, transparent);
+ }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .app-input {
+ transition: none;
+ }
+}
diff --git a/src/app/shared/ui/input/input.component.ts b/src/app/shared/ui/input/input.component.ts
new file mode 100644
index 0000000..69708aa
--- /dev/null
+++ b/src/app/shared/ui/input/input.component.ts
@@ -0,0 +1,64 @@
+import { ChangeDetectionStrategy, Component, forwardRef, input } from '@angular/core';
+import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
+
+export type InputSize = 'sm' | 'md' | 'lg';
+export type InputState = 'default' | 'error' | 'success';
+
+@Component({
+ selector: 'app-input',
+ standalone: true,
+ templateUrl: './input.component.html',
+ styleUrl: './input.component.scss',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ providers: [
+ {
+ provide: NG_VALUE_ACCESSOR,
+ useExisting: forwardRef(() => InputComponent),
+ multi: true
+ }
+ ],
+ host: {
+ '[class.app-input-host--full-width]': 'fullWidth()'
+ }
+})
+export class InputComponent implements ControlValueAccessor {
+ readonly type = input<'text' | 'email' | 'password' | 'number' | 'search' | 'tel' | 'url'>('text');
+ readonly size = input('md');
+ readonly state = input('default');
+ readonly placeholder = input('');
+ readonly disabled = input(false);
+ readonly fullWidth = input(false);
+ readonly ariaLabel = input(null);
+
+ protected value: string | number = '';
+ protected isDisabled = false;
+
+ private onChange: (value: string | number) => void = () => {};
+ private onTouched: () => void = () => {};
+
+ writeValue(value: string | number): void {
+ this.value = value ?? '';
+ }
+
+ registerOnChange(fn: (value: string | number) => void): void {
+ this.onChange = fn;
+ }
+
+ registerOnTouched(fn: () => void): void {
+ this.onTouched = fn;
+ }
+
+ setDisabledState(isDisabled: boolean): void {
+ this.isDisabled = isDisabled;
+ }
+
+ protected handleInput(event: Event): void {
+ const target = event.target as HTMLInputElement;
+ this.value = target.value;
+ this.onChange(this.value);
+ }
+
+ protected handleBlur(): void {
+ this.onTouched();
+ }
+}