diff --git a/src/app/shared/ui/button/button.component.html b/src/app/shared/ui/button/button.component.html
new file mode 100644
index 0000000..61aae18
--- /dev/null
+++ b/src/app/shared/ui/button/button.component.html
@@ -0,0 +1,19 @@
+
diff --git a/src/app/shared/ui/button/button.component.scss b/src/app/shared/ui/button/button.component.scss
new file mode 100644
index 0000000..d4f1c30
--- /dev/null
+++ b/src/app/shared/ui/button/button.component.scss
@@ -0,0 +1,119 @@
+:host {
+ display: inline-block;
+}
+
+:host.app-button-host--full-width {
+ display: block;
+ width: 100%;
+}
+
+.app-button {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: var(--space-xs, 0.25rem);
+ width: 100%;
+ border: 1px solid transparent;
+ border-radius: var(--radius-md, 6px);
+ font-family: inherit;
+ font-weight: 600;
+ line-height: 1;
+ cursor: pointer;
+ transition: background-color var(--transition-fast, 120ms ease),
+ border-color var(--transition-fast, 120ms ease),
+ box-shadow var(--transition-fast, 120ms ease),
+ opacity var(--transition-fast, 120ms ease);
+
+ &:focus-visible {
+ outline: 2px solid var(--primary-color, #2f6e5d);
+ outline-offset: 2px;
+ }
+
+ &:disabled {
+ cursor: not-allowed;
+ opacity: 0.6;
+ }
+}
+
+.app-button--sm {
+ height: 32px;
+ padding: 0 var(--space-sm, 0.5rem);
+ font-size: 0.8125rem;
+}
+
+.app-button--md {
+ height: 40px;
+ padding: 0 var(--space-md, 1rem);
+ font-size: 0.9375rem;
+}
+
+.app-button--lg {
+ height: 48px;
+ padding: 0 var(--space-lg, 1.5rem);
+ font-size: 1rem;
+}
+
+.app-button--primary {
+ background: var(--primary-color, #2f6e5d);
+ border-color: var(--primary-color, #2f6e5d);
+ color: var(--bg-primary, #fff);
+
+ &:hover:not(:disabled) {
+ box-shadow: var(--shadow-sm, 0 1px 2px rgba(0, 0, 0, 0.08));
+ }
+}
+
+.app-button--secondary {
+ background: var(--bg-secondary, #f4f4f5);
+ border-color: var(--border-color, #e4e4e7);
+ color: var(--text-primary, #1f322d);
+
+ &:hover:not(:disabled) {
+ border-color: var(--primary-color, #2f6e5d);
+ }
+}
+
+.app-button--ghost {
+ background: transparent;
+ border-color: transparent;
+ color: var(--text-primary, #1f322d);
+
+ &:hover:not(:disabled) {
+ background: var(--bg-secondary, #f4f4f5);
+ }
+}
+
+.app-button--danger {
+ background: var(--error-color, #c0392b);
+ border-color: var(--error-color, #c0392b);
+ color: var(--bg-primary, #fff);
+
+ &:hover:not(:disabled) {
+ box-shadow: var(--shadow-sm, 0 1px 2px rgba(0, 0, 0, 0.08));
+ }
+}
+
+.app-button__spinner {
+ width: 1em;
+ height: 1em;
+ border: 2px solid currentColor;
+ border-right-color: transparent;
+ border-radius: 50%;
+ animation: app-button-spin 0.6s linear infinite;
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .app-button {
+ transition: none;
+ }
+
+ .app-button__spinner {
+ animation-duration: 1.2s;
+ }
+}
+
+@keyframes app-button-spin {
+ to {
+ transform: rotate(360deg);
+ }
+}
diff --git a/src/app/shared/ui/button/button.component.ts b/src/app/shared/ui/button/button.component.ts
new file mode 100644
index 0000000..8189e22
--- /dev/null
+++ b/src/app/shared/ui/button/button.component.ts
@@ -0,0 +1,23 @@
+import { ChangeDetectionStrategy, Component, input } from '@angular/core';
+
+export type ButtonVariant = 'primary' | 'secondary' | 'ghost' | 'danger';
+export type ButtonSize = 'sm' | 'md' | 'lg';
+
+@Component({
+ selector: 'app-button',
+ standalone: true,
+ templateUrl: './button.component.html',
+ styleUrl: './button.component.scss',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ host: {
+ '[class.app-button-host--full-width]': 'fullWidth()'
+ }
+})
+export class ButtonComponent {
+ readonly variant = input('primary');
+ readonly size = input('md');
+ readonly type = input<'button' | 'submit' | 'reset'>('button');
+ readonly disabled = input(false);
+ readonly loading = input(false);
+ readonly fullWidth = input(false);
+}