diff --git a/src/components/Alert/index.tsx b/src/components/Alert/index.tsx
new file mode 100644
index 0000000..5d44f1f
--- /dev/null
+++ b/src/components/Alert/index.tsx
@@ -0,0 +1,52 @@
+import type { ReactNode } from 'react';
+import clsx from 'clsx';
+import styles from './styles.module.css';
+
+type AlertVariant = 'info' | 'success' | 'warning' | 'error';
+type AlertSize = 'sm' | 'md';
+
+interface AlertProps {
+ variant?: AlertVariant;
+ size?: AlertSize;
+ title?: string;
+ children: ReactNode;
+ className?: string;
+ icon?: ReactNode;
+}
+
+export default function Alert({
+ variant = 'info',
+ size = 'md',
+ title,
+ children,
+ className,
+ icon,
+}: AlertProps): ReactNode {
+ const defaultIcons = {
+ info: 'ℹ️',
+ success: '✓',
+ warning: '⚠',
+ error: '✕',
+ };
+
+ return (
+
+
+ {icon || {defaultIcons[variant]}}
+
+
+ {title &&
{title}
}
+
{children}
+
+
+ );
+}
+
diff --git a/src/components/Alert/styles.module.css b/src/components/Alert/styles.module.css
new file mode 100644
index 0000000..f5abc27
--- /dev/null
+++ b/src/components/Alert/styles.module.css
@@ -0,0 +1,109 @@
+.alert {
+ display: flex;
+ gap: var(--ode-spacing-4);
+ padding: var(--ode-spacing-4);
+ border-radius: var(--ode-border-radius-md);
+ border: var(--ode-border-width-thin) solid;
+ font-family: var(--ode-font-family-sans);
+}
+
+.alert--sm {
+ padding: var(--ode-spacing-3);
+ gap: var(--ode-spacing-3);
+}
+
+.alert--md {
+ padding: var(--ode-spacing-4);
+ gap: var(--ode-spacing-4);
+}
+
+.alertIcon {
+ flex-shrink: 0;
+ display: flex;
+ align-items: flex-start;
+ padding-top: var(--ode-spacing-0_5);
+}
+
+.iconEmoji {
+ font-size: var(--ode-font-size-lg);
+ line-height: 1;
+}
+
+.alertContent {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ gap: var(--ode-spacing-2);
+}
+
+.alertTitle {
+ font-size: var(--ode-font-size-base);
+ font-weight: var(--ode-font-weight-semibold);
+ line-height: var(--ode-line-height-tight);
+}
+
+.alertMessage {
+ font-size: var(--ode-font-size-sm);
+ line-height: var(--ode-line-height-relaxed);
+}
+
+.alert--sm .alertTitle {
+ font-size: var(--ode-font-size-sm);
+}
+
+.alert--sm .alertMessage {
+ font-size: var(--ode-font-size-xs);
+}
+
+/* Info Variant */
+.alert--info {
+ background-color: var(--ode-color-semantic-info-50);
+ border-color: var(--ode-color-semantic-info-500);
+ color: var(--ode-color-semantic-info-600);
+}
+
+[data-theme='dark'] .alert--info {
+ background-color: rgba(33, 150, 243, 0.1);
+ border-color: var(--ode-color-semantic-info-500);
+ color: #90caf9;
+}
+
+/* Success Variant */
+.alert--success {
+ background-color: var(--ode-color-semantic-success-50);
+ border-color: var(--ode-color-semantic-success-500);
+ color: var(--ode-color-semantic-success-600);
+}
+
+[data-theme='dark'] .alert--success {
+ background-color: rgba(52, 199, 89, 0.1);
+ border-color: var(--ode-color-semantic-success-500);
+ color: #81c784;
+}
+
+/* Warning Variant */
+.alert--warning {
+ background-color: var(--ode-color-semantic-warning-50);
+ border-color: var(--ode-color-semantic-warning-500);
+ color: var(--ode-color-semantic-warning-600);
+}
+
+[data-theme='dark'] .alert--warning {
+ background-color: rgba(255, 149, 0, 0.1);
+ border-color: var(--ode-color-semantic-warning-500);
+ color: #ffb74d;
+}
+
+/* Error Variant */
+.alert--error {
+ background-color: var(--ode-color-semantic-error-50);
+ border-color: var(--ode-color-semantic-error-500);
+ color: var(--ode-color-semantic-error-600);
+}
+
+[data-theme='dark'] .alert--error {
+ background-color: rgba(244, 67, 54, 0.1);
+ border-color: var(--ode-color-semantic-error-500);
+ color: #e57373;
+}
+
diff --git a/src/components/Badge/index.tsx b/src/components/Badge/index.tsx
new file mode 100644
index 0000000..23f00cf
--- /dev/null
+++ b/src/components/Badge/index.tsx
@@ -0,0 +1,34 @@
+import type { ReactNode } from 'react';
+import clsx from 'clsx';
+import styles from './styles.module.css';
+
+type BadgeVariant = 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'info' | 'neutral';
+type BadgeSize = 'sm' | 'md';
+
+interface BadgeProps {
+ variant?: BadgeVariant;
+ size?: BadgeSize;
+ children: ReactNode;
+ className?: string;
+}
+
+export default function Badge({
+ variant = 'primary',
+ size = 'md',
+ children,
+ className,
+}: BadgeProps): ReactNode {
+ return (
+
+ {children}
+
+ );
+}
+
diff --git a/src/components/Badge/styles.module.css b/src/components/Badge/styles.module.css
new file mode 100644
index 0000000..7bde82b
--- /dev/null
+++ b/src/components/Badge/styles.module.css
@@ -0,0 +1,99 @@
+.badge {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ font-family: var(--ode-font-family-sans);
+ font-weight: var(--ode-font-weight-medium);
+ line-height: var(--ode-line-height-none);
+ border-radius: var(--ode-border-radius-full);
+ white-space: nowrap;
+}
+
+/* Size Variants */
+.badge--sm {
+ padding: var(--ode-spacing-0_5) var(--ode-spacing-2);
+ font-size: var(--ode-font-size-xs);
+}
+
+.badge--md {
+ padding: var(--ode-spacing-1) var(--ode-spacing-3);
+ font-size: var(--ode-font-size-sm);
+}
+
+/* Primary Variant */
+.badge--primary {
+ background-color: var(--ode-color-brand-primary-100);
+ color: var(--ode-color-brand-primary-700);
+}
+
+[data-theme='dark'] .badge--primary {
+ background-color: var(--ode-color-brand-primary-900);
+ color: var(--ode-color-brand-primary-200);
+}
+
+/* Secondary Variant */
+.badge--secondary {
+ background-color: var(--ode-color-brand-secondary-100);
+ color: var(--ode-color-brand-secondary-800);
+}
+
+[data-theme='dark'] .badge--secondary {
+ background-color: var(--ode-color-brand-secondary-900);
+ color: var(--ode-color-brand-secondary-200);
+}
+
+/* Success Variant */
+.badge--success {
+ background-color: var(--ode-color-semantic-success-50);
+ color: var(--ode-color-semantic-success-600);
+}
+
+[data-theme='dark'] .badge--success {
+ background-color: rgba(46, 125, 50, 0.2);
+ color: #81c784;
+}
+
+/* Warning Variant */
+.badge--warning {
+ background-color: var(--ode-color-semantic-warning-50);
+ color: var(--ode-color-semantic-warning-600);
+}
+
+[data-theme='dark'] .badge--warning {
+ background-color: rgba(217, 119, 6, 0.2);
+ color: #ffb74d;
+}
+
+/* Error Variant */
+.badge--error {
+ background-color: var(--ode-color-semantic-error-50);
+ color: var(--ode-color-semantic-error-600);
+}
+
+[data-theme='dark'] .badge--error {
+ background-color: rgba(220, 38, 38, 0.2);
+ color: #e57373;
+}
+
+/* Info Variant */
+.badge--info {
+ background-color: var(--ode-color-semantic-info-50);
+ color: var(--ode-color-semantic-info-600);
+}
+
+[data-theme='dark'] .badge--info {
+ background-color: rgba(37, 99, 235, 0.2);
+ color: #64b5f6;
+}
+
+/* Neutral Variant */
+.badge--neutral {
+ background-color: var(--ode-color-neutral-100);
+ color: var(--ode-color-neutral-700);
+}
+
+[data-theme='dark'] .badge--neutral {
+ background-color: var(--ode-color-neutral-800);
+ color: var(--ode-color-neutral-200);
+}
+
diff --git a/src/components/Button/index.tsx b/src/components/Button/index.tsx
new file mode 100644
index 0000000..58ae1b0
--- /dev/null
+++ b/src/components/Button/index.tsx
@@ -0,0 +1,73 @@
+import type { ReactNode, ButtonHTMLAttributes } from 'react';
+import Link from '@docusaurus/Link';
+import clsx from 'clsx';
+import styles from './styles.module.css';
+
+type ButtonVariant = 'primary' | 'secondary' | 'outline' | 'ghost' | 'pill-light' | 'pill-dark';
+type ButtonSize = 'sm' | 'md' | 'lg';
+
+interface BaseButtonProps {
+ variant?: ButtonVariant;
+ size?: ButtonSize;
+ children: ReactNode;
+ className?: string;
+ disabled?: boolean;
+}
+
+interface ButtonAsButton extends BaseButtonProps, Omit, 'className' | 'children'> {
+ href?: never;
+ to?: never;
+ onClick?: () => void;
+}
+
+interface ButtonAsLink extends BaseButtonProps {
+ href?: string;
+ to?: string;
+ onClick?: never;
+}
+
+type ButtonProps = ButtonAsButton | ButtonAsLink;
+
+export default function Button({
+ variant = 'primary',
+ size = 'md',
+ children,
+ className,
+ disabled = false,
+ href,
+ to,
+ onClick,
+ ...props
+}: ButtonProps): ReactNode {
+ const classes = clsx(
+ styles.button,
+ styles[`button--${variant}`],
+ styles[`button--${size}`],
+ disabled && styles['button--disabled'],
+ className
+ );
+
+ if (href || to) {
+ return (
+
+ {children}
+
+ );
+ }
+
+ return (
+
+ );
+}
+
diff --git a/src/components/Button/styles.module.css b/src/components/Button/styles.module.css
new file mode 100644
index 0000000..023233f
--- /dev/null
+++ b/src/components/Button/styles.module.css
@@ -0,0 +1,321 @@
+.button {
+ position: relative;
+ display: inline-flex !important;
+ align-items: center !important;
+ justify-content: center !important;
+ font-family: var(--ode-font-family-sans) !important;
+ font-weight: var(--ode-font-weight-button) !important;
+ line-height: var(--ode-line-height-tight) !important;
+ text-decoration: none !important;
+ border: var(--ode-border-width-thin) solid transparent !important;
+ border-radius: var(--ode-border-radius-md) !important;
+ cursor: pointer !important;
+ transition: all var(--ode-duration-normal) var(--ode-easing-ease-out) !important;
+ white-space: nowrap !important;
+ user-select: none !important;
+ background-color: transparent !important;
+ overflow: visible !important;
+ z-index: 0;
+ text-transform: none; /* Default - can be overridden per variant */
+}
+
+.button:hover,
+.button:focus,
+.button:active {
+ text-decoration: none !important;
+}
+
+.button > * {
+ position: relative;
+ z-index: 2;
+}
+
+.button:focus {
+ outline: var(--ode-border-width-medium) solid var(--ifm-color-primary);
+ outline-offset: var(--ode-spacing-1);
+}
+
+.button--disabled {
+ opacity: var(--ode-opacity-50);
+ cursor: not-allowed;
+ pointer-events: none;
+}
+
+/* Primary Variant */
+.button--primary {
+ background-color: var(--ode-color-brand-primary-500) !important;
+ color: var(--ode-color-neutral-white) !important;
+ border-color: var(--ode-color-brand-primary-500) !important;
+}
+
+.button--primary:hover:not(.button--disabled) {
+ background-color: var(--ode-color-brand-primary-600) !important;
+ border-color: var(--ode-color-brand-primary-600) !important;
+ box-shadow: var(--ode-shadow-sm) !important;
+}
+
+.button--primary:active:not(.button--disabled) {
+ background-color: var(--ode-color-brand-primary-700) !important;
+ border-color: var(--ode-color-brand-primary-700) !important;
+}
+
+/* Secondary Variant */
+.button--secondary {
+ background-color: var(--ode-color-brand-secondary-500) !important;
+ color: var(--ode-color-neutral-900) !important;
+ border-color: var(--ode-color-brand-secondary-500) !important;
+}
+
+.button--secondary:hover:not(.button--disabled) {
+ background-color: var(--ode-color-brand-secondary-600) !important;
+ border-color: var(--ode-color-brand-secondary-600) !important;
+ box-shadow: var(--ode-shadow-sm) !important;
+}
+
+.button--secondary:active:not(.button--disabled) {
+ background-color: var(--ode-color-brand-secondary-700) !important;
+ border-color: var(--ode-color-brand-secondary-700) !important;
+}
+
+/* Outline Variant */
+.button--outline {
+ background-color: transparent;
+ color: var(--ode-color-brand-primary-500);
+ border-color: var(--ode-color-brand-primary-500);
+}
+
+.button--outline:hover:not(.button--disabled) {
+ background-color: var(--ode-color-brand-primary-50);
+ color: var(--ode-color-brand-primary-600);
+ border-color: var(--ode-color-brand-primary-600);
+}
+
+[data-theme='dark'] .button--outline {
+ color: var(--ode-color-brand-primary-400);
+ border-color: var(--ode-color-brand-primary-400);
+}
+
+[data-theme='dark'] .button--outline:hover:not(.button--disabled) {
+ background-color: rgba(79, 127, 78, 0.1);
+ color: var(--ode-color-brand-primary-300);
+ border-color: var(--ode-color-brand-primary-300);
+}
+
+/* Ghost Variant */
+.button--ghost {
+ background-color: transparent;
+ color: var(--ode-color-brand-primary-500);
+ border-color: transparent;
+}
+
+.button--ghost:hover:not(.button--disabled) {
+ background-color: var(--ode-color-neutral-100);
+ color: var(--ode-color-brand-primary-600);
+}
+
+[data-theme='dark'] .button--ghost {
+ color: var(--ode-color-brand-primary-400);
+}
+
+[data-theme='dark'] .button--ghost:hover:not(.button--disabled) {
+ background-color: var(--ode-color-neutral-800);
+ color: var(--ode-color-brand-primary-300);
+}
+
+/* Size Variants */
+.button--sm {
+ padding: var(--ode-spacing-2) var(--ode-spacing-4);
+ font-size: var(--ode-font-size-sm);
+ gap: var(--ode-spacing-2);
+}
+
+.button--md {
+ padding: var(--ode-spacing-3) var(--ode-spacing-6);
+ font-size: var(--ode-font-size-base);
+ gap: var(--ode-spacing-2);
+}
+
+.button--lg {
+ padding: var(--ode-spacing-4) var(--ode-spacing-8);
+ font-size: var(--ode-font-size-lg);
+ gap: var(--ode-spacing-3);
+}
+
+/* Pill Light Variant - Secondary color bg, primary color border/text, fade on left */
+.button--pill-light {
+ border-radius: var(--ode-border-radius-full) !important;
+ background-color: var(--ode-color-brand-secondary-500) !important; /* #E9B85B */
+ color: var(--ode-color-brand-primary-500) !important; /* #4F7F4E */
+ border: var(--ode-border-width-thin) solid var(--ode-color-brand-primary-500) !important;
+ position: relative;
+ overflow: visible;
+ text-transform: uppercase !important;
+ letter-spacing: 0.02em !important;
+}
+
+/* Create fading border effect on left side - overlay that fades the border */
+.button--pill-light::after {
+ content: '';
+ position: absolute;
+ top: -1px;
+ left: -1px;
+ width: 30%;
+ height: calc(100% + 2px);
+ border-radius: var(--ode-border-radius-full) 0 0 var(--ode-border-radius-full);
+ background: linear-gradient(
+ to right,
+ var(--ode-color-brand-secondary-500) 0%,
+ rgba(233, 184, 91, 0.99) 8%,
+ rgba(233, 184, 91, 0.95) 16%,
+ rgba(233, 184, 91, 0.88) 25%,
+ rgba(233, 184, 91, 0.75) 35%,
+ rgba(233, 184, 91, 0.6) 50%,
+ rgba(233, 184, 91, 0.4) 65%,
+ rgba(233, 184, 91, 0.2) 80%,
+ transparent 100%
+ );
+ pointer-events: none;
+ z-index: 1;
+}
+
+.button--pill-light:hover:not(.button--disabled) {
+ background-color: var(--ode-color-brand-secondary-400) !important;
+ color: var(--ode-color-brand-primary-600) !important;
+ border-color: var(--ode-color-brand-primary-600) !important;
+}
+
+.button--pill-light:hover:not(.button--disabled)::after {
+ background: linear-gradient(
+ to right,
+ var(--ode-color-brand-secondary-400) 0%,
+ rgba(240, 184, 77, 0.99) 8%,
+ rgba(240, 184, 77, 0.95) 16%,
+ rgba(240, 184, 77, 0.88) 25%,
+ rgba(240, 184, 77, 0.75) 35%,
+ rgba(240, 184, 77, 0.6) 50%,
+ rgba(240, 184, 77, 0.4) 65%,
+ rgba(240, 184, 77, 0.2) 80%,
+ transparent 100%
+ );
+}
+
+[data-theme='dark'] .button--pill-light {
+ background-color: var(--ode-color-brand-secondary-500) !important;
+ color: var(--ode-color-brand-primary-400) !important;
+ border-color: var(--ode-color-brand-primary-400) !important;
+}
+
+[data-theme='dark'] .button--pill-light::after {
+ background: linear-gradient(
+ to right,
+ var(--ode-color-brand-secondary-500) 0%,
+ rgba(233, 184, 91, 0.99) 8%,
+ rgba(233, 184, 91, 0.95) 16%,
+ rgba(233, 184, 91, 0.88) 25%,
+ rgba(233, 184, 91, 0.75) 35%,
+ rgba(233, 184, 91, 0.6) 50%,
+ rgba(233, 184, 91, 0.4) 65%,
+ rgba(233, 184, 91, 0.2) 80%,
+ transparent 100%
+ );
+}
+
+[data-theme='dark'] .button--pill-light:hover:not(.button--disabled) {
+ background-color: var(--ode-color-brand-secondary-400) !important;
+ color: var(--ode-color-brand-primary-300) !important;
+ border-color: var(--ode-color-brand-primary-300) !important;
+}
+
+/* Pill Dark Variant - Primary color bg, white text, secondary color border, fade on right */
+.button--pill-dark {
+ border-radius: var(--ode-border-radius-full) !important;
+ background-color: var(--ode-color-brand-primary-500) !important; /* #4F7F4E */
+ color: var(--ode-color-neutral-white) !important;
+ border: var(--ode-border-width-thin) solid var(--ode-color-brand-secondary-500) !important; /* #E9B85B */
+ position: relative;
+ overflow: visible;
+ text-transform: uppercase !important;
+ letter-spacing: 0.02em !important;
+}
+
+/* Create fading border effect on right side - overlay that fades the border */
+.button--pill-dark::after {
+ content: '';
+ position: absolute;
+ top: -1px;
+ right: -1px;
+ width: 30%;
+ height: calc(100% + 2px);
+ border-radius: 0 var(--ode-border-radius-full) var(--ode-border-radius-full) 0;
+ background: linear-gradient(
+ to left,
+ var(--ode-color-brand-primary-500) 0%,
+ rgba(79, 127, 78, 0.99) 8%,
+ rgba(79, 127, 78, 0.95) 16%,
+ rgba(79, 127, 78, 0.88) 25%,
+ rgba(79, 127, 78, 0.75) 35%,
+ rgba(79, 127, 78, 0.6) 50%,
+ rgba(79, 127, 78, 0.4) 65%,
+ rgba(79, 127, 78, 0.2) 80%,
+ transparent 100%
+ );
+ pointer-events: none;
+ z-index: 1;
+}
+
+.button--pill-dark:hover:not(.button--disabled) {
+ background-color: var(--ode-color-brand-primary-600) !important;
+ color: var(--ode-color-neutral-white) !important;
+ border-color: var(--ode-color-brand-secondary-400) !important;
+}
+
+.button--pill-dark:hover:not(.button--disabled)::after {
+ background: linear-gradient(
+ to left,
+ var(--ode-color-brand-primary-600) 0%,
+ rgba(63, 106, 62, 0.99) 8%,
+ rgba(63, 106, 62, 0.95) 16%,
+ rgba(63, 106, 62, 0.88) 25%,
+ rgba(63, 106, 62, 0.75) 35%,
+ rgba(63, 106, 62, 0.6) 50%,
+ rgba(63, 106, 62, 0.4) 65%,
+ rgba(63, 106, 62, 0.2) 80%,
+ transparent 100%
+ );
+}
+
+[data-theme='dark'] .button--pill-dark {
+ background-color: var(--ode-color-brand-primary-500) !important;
+ color: var(--ode-color-neutral-white) !important;
+ border-color: var(--ode-color-brand-secondary-500) !important;
+}
+
+[data-theme='dark'] .button--pill-dark::after {
+ background: linear-gradient(
+ to left,
+ var(--ode-color-brand-primary-500) 0%,
+ rgba(79, 127, 78, 0.99) 8%,
+ rgba(79, 127, 78, 0.95) 16%,
+ rgba(79, 127, 78, 0.88) 25%,
+ rgba(79, 127, 78, 0.75) 35%,
+ rgba(79, 127, 78, 0.6) 50%,
+ rgba(79, 127, 78, 0.4) 65%,
+ rgba(79, 127, 78, 0.2) 80%,
+ transparent 100%
+ );
+}
+
+[data-theme='dark'] .button--pill-dark:hover:not(.button--disabled) {
+ background-color: var(--ode-color-brand-primary-400) !important;
+ color: var(--ode-color-neutral-white) !important;
+ border-color: var(--ode-color-brand-secondary-400) !important;
+}
+
+/* Responsive */
+@media (max-width: 768px) {
+ .button--lg {
+ padding: var(--ode-spacing-3) var(--ode-spacing-6);
+ font-size: var(--ode-font-size-base);
+ }
+}
+
diff --git a/src/components/ButtonGroup/index.tsx b/src/components/ButtonGroup/index.tsx
new file mode 100644
index 0000000..1fec749
--- /dev/null
+++ b/src/components/ButtonGroup/index.tsx
@@ -0,0 +1,19 @@
+import type { ReactNode } from 'react';
+import clsx from 'clsx';
+import styles from './styles.module.css';
+
+interface ButtonGroupProps {
+ children: ReactNode;
+ className?: string;
+}
+
+export default function ButtonGroup({
+ children,
+ className,
+}: ButtonGroupProps): ReactNode {
+ return (
+
+ {children}
+
+ );
+}
diff --git a/src/components/ButtonGroup/styles.module.css b/src/components/ButtonGroup/styles.module.css
new file mode 100644
index 0000000..90fb998
--- /dev/null
+++ b/src/components/ButtonGroup/styles.module.css
@@ -0,0 +1,16 @@
+.buttonGroup {
+ display: inline-flex;
+ gap: var(--ode-spacing-4);
+ align-items: center;
+}
+
+@media (max-width: 768px) {
+ .buttonGroup {
+ flex-direction: column;
+ width: 100%;
+ }
+
+ .buttonGroup > * {
+ width: 100%;
+ }
+}
diff --git a/src/components/CodeBlock/index.tsx b/src/components/CodeBlock/index.tsx
new file mode 100644
index 0000000..1967f13
--- /dev/null
+++ b/src/components/CodeBlock/index.tsx
@@ -0,0 +1,44 @@
+import type { ReactNode } from 'react';
+import clsx from 'clsx';
+import styles from './styles.module.css';
+
+interface CodeBlockProps {
+ children: ReactNode;
+ language?: string;
+ title?: string;
+ className?: string;
+ showLineNumbers?: boolean;
+}
+
+export default function CodeBlock({
+ children,
+ language,
+ title,
+ className,
+ showLineNumbers = false,
+}: CodeBlockProps): ReactNode {
+ return (
+
+ {title && (
+
+ {language && (
+ {language}
+ )}
+ {title}
+
+ )}
+
+
+ );
+}
diff --git a/src/components/CodeBlock/styles.module.css b/src/components/CodeBlock/styles.module.css
new file mode 100644
index 0000000..183dd10
--- /dev/null
+++ b/src/components/CodeBlock/styles.module.css
@@ -0,0 +1,110 @@
+.codeBlockWrapper {
+ margin: var(--ode-spacing-6) 0;
+ border-radius: var(--ode-border-radius-md);
+ overflow: hidden;
+ border: var(--ode-border-width-thin) solid var(--ode-color-neutral-300);
+ background-color: var(--ode-color-neutral-50);
+}
+
+[data-theme='dark'] .codeBlockWrapper {
+ border-color: var(--ode-color-neutral-700);
+ background-color: var(--ode-color-neutral-900);
+}
+
+.codeBlockHeader {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: var(--ode-spacing-3) var(--ode-spacing-4);
+ background-color: var(--ode-color-neutral-100);
+ border-bottom: var(--ode-border-width-thin) solid var(--ode-color-neutral-300);
+ font-family: var(--ode-font-family-sans);
+ font-size: var(--ode-font-size-sm);
+}
+
+[data-theme='dark'] .codeBlockHeader {
+ background-color: var(--ode-color-neutral-800);
+ border-bottom-color: var(--ode-color-neutral-700);
+}
+
+.codeBlockLanguage {
+ font-weight: var(--ode-font-weight-medium);
+ color: var(--ode-color-neutral-600);
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ font-size: var(--ode-font-size-xs);
+}
+
+[data-theme='dark'] .codeBlockLanguage {
+ color: var(--ode-color-neutral-400);
+}
+
+.codeBlockTitle {
+ font-weight: var(--ode-font-weight-regular);
+ color: var(--ode-color-neutral-700);
+ flex: 1;
+ text-align: right;
+}
+
+[data-theme='dark'] .codeBlockTitle {
+ color: var(--ode-color-neutral-300);
+}
+
+.codeBlockContainer {
+ overflow-x: auto;
+ background-color: var(--ode-color-neutral-50);
+}
+
+[data-theme='dark'] .codeBlockContainer {
+ background-color: var(--ode-color-neutral-900);
+}
+
+.codeBlock {
+ margin: 0;
+ padding: var(--ode-spacing-4);
+ font-family: var(--ode-font-family-mono);
+ font-size: var(--ode-font-size-sm);
+ line-height: var(--ode-line-height-relaxed);
+ color: var(--ode-color-neutral-900);
+ background: transparent;
+ overflow-x: auto;
+}
+
+[data-theme='dark'] .codeBlock {
+ color: var(--ode-color-neutral-100);
+}
+
+.codeBlock code {
+ font-family: inherit;
+ font-size: inherit;
+ color: inherit;
+ background: transparent;
+ padding: 0;
+ border-radius: 0;
+}
+
+.codeBlock--lineNumbers {
+ counter-reset: line;
+}
+
+.codeBlock--lineNumbers code {
+ display: block;
+ padding-left: var(--ode-spacing-12);
+ position: relative;
+}
+
+.codeBlock--lineNumbers code::before {
+ counter-increment: line;
+ content: counter(line);
+ position: absolute;
+ left: 0;
+ width: var(--ode-spacing-8);
+ text-align: right;
+ padding-right: var(--ode-spacing-4);
+ color: var(--ode-color-neutral-500);
+ user-select: none;
+}
+
+[data-theme='dark'] .codeBlock--lineNumbers code::before {
+ color: var(--ode-color-neutral-600);
+}
diff --git a/src/components/Container/index.tsx b/src/components/Container/index.tsx
new file mode 100644
index 0000000..7d4867f
--- /dev/null
+++ b/src/components/Container/index.tsx
@@ -0,0 +1,29 @@
+import type { ReactNode } from 'react';
+import clsx from 'clsx';
+import styles from './styles.module.css';
+
+type ContainerSize = 'sm' | 'md' | 'lg' | 'xl' | '2xl' | 'full';
+
+interface ContainerProps {
+ size?: ContainerSize;
+ children: ReactNode;
+ className?: string;
+}
+
+export default function Container({
+ size = 'lg',
+ children,
+ className,
+}: ContainerProps): ReactNode {
+ return (
+
+ {children}
+
+ );
+}
diff --git a/src/components/Container/styles.module.css b/src/components/Container/styles.module.css
new file mode 100644
index 0000000..e258fc3
--- /dev/null
+++ b/src/components/Container/styles.module.css
@@ -0,0 +1,45 @@
+.container {
+ width: 100%;
+ margin-left: auto;
+ margin-right: auto;
+ padding-left: var(--ode-spacing-4);
+ padding-right: var(--ode-spacing-4);
+}
+
+.container--sm {
+ max-width: var(--ode-container-sm);
+}
+
+.container--md {
+ max-width: var(--ode-container-md);
+}
+
+.container--lg {
+ max-width: var(--ode-container-lg);
+}
+
+.container--xl {
+ max-width: var(--ode-container-xl);
+}
+
+.container--2xl {
+ max-width: var(--ode-container-2xl);
+}
+
+.container--full {
+ max-width: 100%;
+}
+
+@media (min-width: 640px) {
+ .container {
+ padding-left: var(--ode-spacing-6);
+ padding-right: var(--ode-spacing-6);
+ }
+}
+
+@media (min-width: 1024px) {
+ .container {
+ padding-left: var(--ode-spacing-8);
+ padding-right: var(--ode-spacing-8);
+ }
+}
diff --git a/src/components/Divider/index.tsx b/src/components/Divider/index.tsx
new file mode 100644
index 0000000..14e7b61
--- /dev/null
+++ b/src/components/Divider/index.tsx
@@ -0,0 +1,52 @@
+import type { ReactNode } from 'react';
+import clsx from 'clsx';
+import styles from './styles.module.css';
+
+type DividerVariant = 'solid' | 'dashed' | 'dotted';
+type DividerOrientation = 'horizontal' | 'vertical';
+
+interface DividerProps {
+ variant?: DividerVariant;
+ orientation?: DividerOrientation;
+ spacing?: 'none' | 'sm' | 'md' | 'lg';
+ className?: string;
+ children?: ReactNode;
+}
+
+export default function Divider({
+ variant = 'solid',
+ orientation = 'horizontal',
+ spacing = 'md',
+ className,
+ children,
+}: DividerProps): ReactNode {
+ if (children) {
+ return (
+
+ );
+ }
+
+ return (
+
+ );
+}
+
diff --git a/src/components/Divider/styles.module.css b/src/components/Divider/styles.module.css
new file mode 100644
index 0000000..b25f778
--- /dev/null
+++ b/src/components/Divider/styles.module.css
@@ -0,0 +1,81 @@
+.divider,
+.dividerLine {
+ border: none;
+ border-top: var(--ode-border-width-thin) solid var(--ode-color-neutral-300);
+ margin: 0;
+}
+
+[data-theme='dark'] .divider,
+[data-theme='dark'] .dividerLine {
+ border-top-color: var(--ode-color-neutral-700);
+}
+
+.divider--dashed {
+ border-style: dashed;
+}
+
+.divider--dotted {
+ border-style: dotted;
+}
+
+.divider--horizontal {
+ width: 100%;
+ height: 0;
+}
+
+.divider--vertical {
+ width: 0;
+ height: 100%;
+ border-top: none;
+ border-left: var(--ode-border-width-thin) solid var(--ode-color-neutral-300);
+}
+
+[data-theme='dark'] .divider--vertical {
+ border-left-color: var(--ode-color-neutral-700);
+}
+
+/* Spacing Variants */
+.divider--spacing-none {
+ margin: 0;
+}
+
+.divider--spacing-sm {
+ margin: var(--ode-spacing-4) 0;
+}
+
+.divider--spacing-md {
+ margin: var(--ode-spacing-8) 0;
+}
+
+.divider--spacing-lg {
+ margin: var(--ode-spacing-12) 0;
+}
+
+.dividerWithText {
+ display: flex;
+ align-items: center;
+ width: 100%;
+ gap: var(--ode-spacing-4);
+}
+
+.dividerWithText .dividerLine {
+ flex: 1;
+ border-top: var(--ode-border-width-thin) solid var(--ode-color-neutral-300);
+}
+
+[data-theme='dark'] .dividerWithText .dividerLine {
+ border-top-color: var(--ode-color-neutral-700);
+}
+
+.dividerText {
+ font-family: var(--ode-font-family-sans);
+ font-size: var(--ode-font-size-sm);
+ color: var(--ode-color-neutral-600);
+ white-space: nowrap;
+ flex-shrink: 0;
+}
+
+[data-theme='dark'] .dividerText {
+ color: var(--ode-color-neutral-400);
+}
+
diff --git a/src/components/README.md b/src/components/README.md
new file mode 100644
index 0000000..7d9f1d0
--- /dev/null
+++ b/src/components/README.md
@@ -0,0 +1,251 @@
+# ODE Design System Components
+
+A collection of reusable React components built with ODE design tokens. All components support dark mode, are fully accessible, and follow the ODE design system specifications.
+
+## Components
+
+### Alert
+Display contextual feedback messages with semantic variants.
+
+```tsx
+import { Alert } from '@site/src/components';
+
+
+ This is an informational message.
+
+
+Operation completed successfully!
+Please review your input.
+Something went wrong.
+```
+
+**Props:**
+- `variant`: `'info' | 'success' | 'warning' | 'error'` (default: `'info'`)
+- `size`: `'sm' | 'md'` (default: `'md'`)
+- `title`: Optional title text
+- `icon`: Optional custom icon element
+- `children`: Alert message content
+
+---
+
+### Badge
+Small status indicators and labels.
+
+```tsx
+import { Badge } from '@site/src/components';
+
+New
+Active
+Pending
+```
+
+**Props:**
+- `variant`: `'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'info' | 'neutral'` (default: `'primary'`)
+- `size`: `'sm' | 'md'` (default: `'md'`)
+
+---
+
+### Button
+Interactive button component with multiple variants and sizes.
+
+```tsx
+import { Button, ButtonGroup } from '@site/src/components';
+
+
+
+
+
+
+{/* Pill buttons with fading borders - perfect for pairs */}
+
+
+
+
+
+
+```
+
+**Props:**
+- `variant`: `'primary' | 'secondary' | 'outline' | 'ghost' | 'pill-light' | 'pill-dark'` (default: `'primary'`)
+ - `pill-light`: Light style with transparent background, dark border/text, fade on left side
+ - `pill-dark`: Dark style with filled background, light text, fade on right side
+- `size`: `'sm' | 'md' | 'lg'` (default: `'md'`)
+- `disabled`: Boolean
+- `href` or `to`: Renders as a link instead of button
+- `onClick`: Click handler (when used as button)
+
+**Pill Button Behavior:**
+- `pill-light`: Default state has transparent background with dark border/text. On hover, fills with light background.
+- `pill-dark`: Default state has dark filled background with light text. On hover, darkens further.
+- When paired together, they create opposite visual states perfect for toggle or action pairs.
+
+---
+
+### CodeBlock
+Enhanced code block with optional title and line numbers.
+
+```tsx
+import { CodeBlock } from '@site/src/components';
+
+
+{`function greet(name: string) {
+ return \`Hello, \${name}!\`;
+}`}
+
+```
+
+**Props:**
+- `language`: Code language for syntax highlighting
+- `title`: Optional title shown in header
+- `showLineNumbers`: Boolean (default: `false`)
+
+---
+
+### Container
+Responsive container with max-width constraints.
+
+```tsx
+import { Container } from '@site/src/components';
+
+
+ Content
+
+```
+
+**Props:**
+- `size`: `'sm' | 'md' | 'lg' | 'xl' | '2xl' | 'full'` (default: `'lg'`)
+
+---
+
+### Divider
+Visual separator with optional text.
+
+```tsx
+import { Divider } from '@site/src/components';
+
+
+
+Or
+
+```
+
+**Props:**
+- `variant`: `'solid' | 'dashed' | 'dotted'` (default: `'solid'`)
+- `orientation`: `'horizontal' | 'vertical'` (default: `'horizontal'`)
+- `spacing`: `'none' | 'sm' | 'md' | 'lg'` (default: `'md'`)
+- `children`: Optional text to display in center
+
+---
+
+### Spacer
+Consistent spacing utility component.
+
+```tsx
+import { Spacer } from '@site/src/components';
+
+
+
+
+```
+
+**Props:**
+- `size`: `0 | 1 | 2 | 3 | 4 | 5 | 6 | 8 | 10 | 12 | 16 | 20 | 24`
+- `axis`: `'x' | 'y' | 'both'` (default: `'both'`)
+
+---
+
+### Tag
+Categorization tags with optional remove functionality.
+
+```tsx
+import { Tag } from '@site/src/components';
+
+React
+TypeScript
+ console.log('removed')}>
+ Removable
+
+```
+
+**Props:**
+- `variant`: `'primary' | 'secondary' | 'neutral'` (default: `'neutral'`)
+- `size`: `'sm' | 'md'` (default: `'md'`)
+- `removable`: Boolean (default: `false`)
+- `onRemove`: Callback when remove button is clicked
+
+---
+
+### Tooltip
+Contextual information on hover/focus.
+
+```tsx
+import { Tooltip } from '@site/src/components';
+
+
+
+
+```
+
+**Props:**
+- `content`: Tooltip content
+- `position`: `'top' | 'bottom' | 'left' | 'right'` (default: `'top'`)
+- `delay`: Delay in milliseconds before showing (default: `200`)
+
+---
+
+### ButtonGroup
+Container for grouping buttons together, especially useful for pill button pairs.
+
+```tsx
+import { ButtonGroup, Button } from '@site/src/components';
+
+
+
+
+
+```
+
+**Props:**
+- `children`: Button elements to group together
+- `className`: Optional additional CSS classes
+
+---
+
+## Design Tokens
+
+All components use ODE design tokens defined in `src/css/ode-tokens.css`. These include:
+
+- **Colors**: Brand (primary/secondary), semantic (success/error/warning/info), neutral
+- **Spacing**: 4px base unit scale (0-24)
+- **Typography**: Font families, sizes, weights, line heights
+- **Borders**: Radius and width variants
+- **Shadows**: Elevation levels
+- **Motion**: Duration and easing functions
+- **Layout**: Breakpoints and container max-widths
+
+## Dark Mode
+
+All components automatically support dark mode through the `[data-theme='dark']` selector. No additional configuration needed.
+
+## Accessibility
+
+Components follow accessibility best practices:
+- Proper ARIA attributes
+- Keyboard navigation support
+- Focus management
+- Semantic HTML elements
+- Color contrast compliance
+
+## Usage in MDX
+
+Components can be imported and used directly in MDX files:
+
+```mdx
+import { Alert, Button, Badge } from '@site/src/components';
+
+
+ This works in MDX!
+
+
+
+```
diff --git a/src/components/Spacer/index.tsx b/src/components/Spacer/index.tsx
new file mode 100644
index 0000000..4407aaf
--- /dev/null
+++ b/src/components/Spacer/index.tsx
@@ -0,0 +1,31 @@
+import type { ReactElement } from 'react';
+import clsx from 'clsx';
+import styles from './styles.module.css';
+
+type SpacerSize = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 8 | 10 | 12 | 16 | 20 | 24;
+type SpacerAxis = 'x' | 'y' | 'both';
+
+interface SpacerProps {
+ size: SpacerSize;
+ axis?: SpacerAxis;
+ className?: string;
+}
+
+export default function Spacer({
+ size,
+ axis = 'both',
+ className,
+}: SpacerProps): React.ReactElement {
+ return (
+
+ );
+}
+
diff --git a/src/components/Spacer/styles.module.css b/src/components/Spacer/styles.module.css
new file mode 100644
index 0000000..5a50ebe
--- /dev/null
+++ b/src/components/Spacer/styles.module.css
@@ -0,0 +1,115 @@
+.spacer {
+ flex-shrink: 0;
+}
+
+/* Axis Variants */
+.spacer--x {
+ width: 100%;
+ height: 0;
+}
+
+.spacer--y {
+ width: 0;
+ height: 100%;
+}
+
+.spacer--both {
+ width: 100%;
+ height: 100%;
+}
+
+/* Size Variants */
+.spacer--0 {
+ width: var(--ode-spacing-0);
+ height: var(--ode-spacing-0);
+}
+
+.spacer--1 {
+ width: var(--ode-spacing-1);
+ height: var(--ode-spacing-1);
+}
+
+.spacer--2 {
+ width: var(--ode-spacing-2);
+ height: var(--ode-spacing-2);
+}
+
+.spacer--3 {
+ width: var(--ode-spacing-3);
+ height: var(--ode-spacing-3);
+}
+
+.spacer--4 {
+ width: var(--ode-spacing-4);
+ height: var(--ode-spacing-4);
+}
+
+.spacer--5 {
+ width: var(--ode-spacing-5);
+ height: var(--ode-spacing-5);
+}
+
+.spacer--6 {
+ width: var(--ode-spacing-6);
+ height: var(--ode-spacing-6);
+}
+
+.spacer--8 {
+ width: var(--ode-spacing-8);
+ height: var(--ode-spacing-8);
+}
+
+.spacer--10 {
+ width: var(--ode-spacing-10);
+ height: var(--ode-spacing-10);
+}
+
+.spacer--12 {
+ width: var(--ode-spacing-12);
+ height: var(--ode-spacing-12);
+}
+
+.spacer--16 {
+ width: var(--ode-spacing-16);
+ height: var(--ode-spacing-16);
+}
+
+.spacer--20 {
+ width: var(--ode-spacing-20);
+ height: var(--ode-spacing-20);
+}
+
+.spacer--24 {
+ width: var(--ode-spacing-24);
+ height: var(--ode-spacing-24);
+}
+
+/* Axis-specific overrides */
+.spacer--x.spacer--0 { height: 0; }
+.spacer--x.spacer--1 { height: 0; width: var(--ode-spacing-1); }
+.spacer--x.spacer--2 { height: 0; width: var(--ode-spacing-2); }
+.spacer--x.spacer--3 { height: 0; width: var(--ode-spacing-3); }
+.spacer--x.spacer--4 { height: 0; width: var(--ode-spacing-4); }
+.spacer--x.spacer--5 { height: 0; width: var(--ode-spacing-5); }
+.spacer--x.spacer--6 { height: 0; width: var(--ode-spacing-6); }
+.spacer--x.spacer--8 { height: 0; width: var(--ode-spacing-8); }
+.spacer--x.spacer--10 { height: 0; width: var(--ode-spacing-10); }
+.spacer--x.spacer--12 { height: 0; width: var(--ode-spacing-12); }
+.spacer--x.spacer--16 { height: 0; width: var(--ode-spacing-16); }
+.spacer--x.spacer--20 { height: 0; width: var(--ode-spacing-20); }
+.spacer--x.spacer--24 { height: 0; width: var(--ode-spacing-24); }
+
+.spacer--y.spacer--0 { width: 0; }
+.spacer--y.spacer--1 { width: 0; height: var(--ode-spacing-1); }
+.spacer--y.spacer--2 { width: 0; height: var(--ode-spacing-2); }
+.spacer--y.spacer--3 { width: 0; height: var(--ode-spacing-3); }
+.spacer--y.spacer--4 { width: 0; height: var(--ode-spacing-4); }
+.spacer--y.spacer--5 { width: 0; height: var(--ode-spacing-5); }
+.spacer--y.spacer--6 { width: 0; height: var(--ode-spacing-6); }
+.spacer--y.spacer--8 { width: 0; height: var(--ode-spacing-8); }
+.spacer--y.spacer--10 { width: 0; height: var(--ode-spacing-10); }
+.spacer--y.spacer--12 { width: 0; height: var(--ode-spacing-12); }
+.spacer--y.spacer--16 { width: 0; height: var(--ode-spacing-16); }
+.spacer--y.spacer--20 { width: 0; height: var(--ode-spacing-20); }
+.spacer--y.spacer--24 { width: 0; height: var(--ode-spacing-24); }
+
diff --git a/src/components/Tag/index.tsx b/src/components/Tag/index.tsx
new file mode 100644
index 0000000..2d7bd66
--- /dev/null
+++ b/src/components/Tag/index.tsx
@@ -0,0 +1,48 @@
+import type { ReactNode } from 'react';
+import clsx from 'clsx';
+import styles from './styles.module.css';
+
+type TagVariant = 'primary' | 'secondary' | 'neutral';
+type TagSize = 'sm' | 'md';
+
+interface TagProps {
+ variant?: TagVariant;
+ size?: TagSize;
+ children: ReactNode;
+ className?: string;
+ removable?: boolean;
+ onRemove?: () => void;
+}
+
+export default function Tag({
+ variant = 'neutral',
+ size = 'md',
+ children,
+ className,
+ removable = false,
+ onRemove,
+}: TagProps): ReactNode {
+ return (
+
+ {children}
+ {removable && (
+
+ )}
+
+ );
+}
+
diff --git a/src/components/Tag/styles.module.css b/src/components/Tag/styles.module.css
new file mode 100644
index 0000000..d330f3e
--- /dev/null
+++ b/src/components/Tag/styles.module.css
@@ -0,0 +1,93 @@
+.tag {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--ode-spacing-2);
+ font-family: var(--ode-font-family-sans);
+ font-weight: var(--ode-font-weight-regular);
+ line-height: var(--ode-line-height-tight);
+ border-radius: var(--ode-border-radius-md);
+ border: var(--ode-border-width-thin) solid;
+}
+
+/* Size Variants */
+.tag--sm {
+ padding: var(--ode-spacing-1) var(--ode-spacing-2_5);
+ font-size: var(--ode-font-size-xs);
+}
+
+.tag--md {
+ padding: var(--ode-spacing-2) var(--ode-spacing-3);
+ font-size: var(--ode-font-size-sm);
+}
+
+.tagContent {
+ display: inline-flex;
+ align-items: center;
+}
+
+.tagRemove {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ margin-left: var(--ode-spacing-1);
+ padding: 0;
+ background: none;
+ border: none;
+ cursor: pointer;
+ font-size: var(--ode-font-size-lg);
+ line-height: 1;
+ opacity: var(--ode-opacity-60);
+ transition: opacity var(--ode-duration-fast) var(--ode-easing-ease-out);
+ width: 1em;
+ height: 1em;
+}
+
+.tagRemove:hover {
+ opacity: var(--ode-opacity-100);
+}
+
+.tagRemove:focus {
+ outline: var(--ode-border-width-medium) solid currentColor;
+ outline-offset: var(--ode-spacing-0_5);
+ border-radius: var(--ode-border-radius-sm);
+}
+
+/* Primary Variant */
+.tag--primary {
+ background-color: var(--ode-color-brand-primary-50);
+ border-color: var(--ode-color-brand-primary-200);
+ color: var(--ode-color-brand-primary-700);
+}
+
+[data-theme='dark'] .tag--primary {
+ background-color: var(--ode-color-brand-primary-900);
+ border-color: var(--ode-color-brand-primary-700);
+ color: var(--ode-color-brand-primary-200);
+}
+
+/* Secondary Variant */
+.tag--secondary {
+ background-color: var(--ode-color-brand-secondary-50);
+ border-color: var(--ode-color-brand-secondary-200);
+ color: var(--ode-color-brand-secondary-800);
+}
+
+[data-theme='dark'] .tag--secondary {
+ background-color: var(--ode-color-brand-secondary-900);
+ border-color: var(--ode-color-brand-secondary-700);
+ color: var(--ode-color-brand-secondary-200);
+}
+
+/* Neutral Variant */
+.tag--neutral {
+ background-color: var(--ode-color-neutral-100);
+ border-color: var(--ode-color-neutral-300);
+ color: var(--ode-color-neutral-700);
+}
+
+[data-theme='dark'] .tag--neutral {
+ background-color: var(--ode-color-neutral-800);
+ border-color: var(--ode-color-neutral-600);
+ color: var(--ode-color-neutral-200);
+}
+
diff --git a/src/components/Tooltip/index.tsx b/src/components/Tooltip/index.tsx
new file mode 100644
index 0000000..6f108df
--- /dev/null
+++ b/src/components/Tooltip/index.tsx
@@ -0,0 +1,63 @@
+import type { ReactNode } from 'react';
+import { useState } from 'react';
+import clsx from 'clsx';
+import styles from './styles.module.css';
+
+type TooltipPosition = 'top' | 'bottom' | 'left' | 'right';
+
+interface TooltipProps {
+ content: ReactNode;
+ position?: TooltipPosition;
+ children: ReactNode;
+ className?: string;
+ delay?: number;
+}
+
+export default function Tooltip({
+ content,
+ position = 'top',
+ children,
+ className,
+ delay = 200,
+}: TooltipProps): ReactNode {
+ const [isVisible, setIsVisible] = useState(false);
+ const [timeoutId, setTimeoutId] = useState(null);
+
+ const showTooltip = () => {
+ const id = setTimeout(() => {
+ setIsVisible(true);
+ }, delay);
+ setTimeoutId(id);
+ };
+
+ const hideTooltip = () => {
+ if (timeoutId) {
+ clearTimeout(timeoutId);
+ setTimeoutId(null);
+ }
+ setIsVisible(false);
+ };
+
+ return (
+
+ {children}
+ {isVisible && (
+
+ {content}
+
+ )}
+
+ );
+}
diff --git a/src/components/Tooltip/styles.module.css b/src/components/Tooltip/styles.module.css
new file mode 100644
index 0000000..8d81207
--- /dev/null
+++ b/src/components/Tooltip/styles.module.css
@@ -0,0 +1,122 @@
+.tooltipWrapper {
+ position: relative;
+ display: inline-block;
+}
+
+.tooltip {
+ position: absolute;
+ z-index: 1000;
+ padding: var(--ode-spacing-2) var(--ode-spacing-3);
+ background-color: var(--ode-color-neutral-900);
+ color: var(--ode-color-neutral-white);
+ font-family: var(--ode-font-family-sans);
+ font-size: var(--ode-font-size-sm);
+ font-weight: var(--ode-font-weight-regular);
+ line-height: var(--ode-line-height-normal);
+ border-radius: var(--ode-border-radius-md);
+ box-shadow: var(--ode-shadow-lg);
+ white-space: nowrap;
+ pointer-events: none;
+ animation: fadeIn var(--ode-duration-fast) var(--ode-easing-ease-out);
+}
+
+[data-theme='dark'] .tooltip {
+ background-color: var(--ode-color-neutral-800);
+ color: var(--ode-color-neutral-100);
+}
+
+@keyframes fadeIn {
+ from {
+ opacity: 0;
+ transform: translateY(-4px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+/* Position Variants */
+.tooltip--top {
+ bottom: 100%;
+ left: 50%;
+ transform: translateX(-50%);
+ margin-bottom: var(--ode-spacing-2);
+}
+
+.tooltip--top::after {
+ content: '';
+ position: absolute;
+ top: 100%;
+ left: 50%;
+ transform: translateX(-50%);
+ border: var(--ode-spacing-2) solid transparent;
+ border-top-color: var(--ode-color-neutral-900);
+}
+
+[data-theme='dark'] .tooltip--top::after {
+ border-top-color: var(--ode-color-neutral-800);
+}
+
+.tooltip--bottom {
+ top: 100%;
+ left: 50%;
+ transform: translateX(-50%);
+ margin-top: var(--ode-spacing-2);
+}
+
+.tooltip--bottom::after {
+ content: '';
+ position: absolute;
+ bottom: 100%;
+ left: 50%;
+ transform: translateX(-50%);
+ border: var(--ode-spacing-2) solid transparent;
+ border-bottom-color: var(--ode-color-neutral-900);
+}
+
+[data-theme='dark'] .tooltip--bottom::after {
+ border-bottom-color: var(--ode-color-neutral-800);
+}
+
+.tooltip--left {
+ right: 100%;
+ top: 50%;
+ transform: translateY(-50%);
+ margin-right: var(--ode-spacing-2);
+}
+
+.tooltip--left::after {
+ content: '';
+ position: absolute;
+ left: 100%;
+ top: 50%;
+ transform: translateY(-50%);
+ border: var(--ode-spacing-2) solid transparent;
+ border-left-color: var(--ode-color-neutral-900);
+}
+
+[data-theme='dark'] .tooltip--left::after {
+ border-left-color: var(--ode-color-neutral-800);
+}
+
+.tooltip--right {
+ left: 100%;
+ top: 50%;
+ transform: translateY(-50%);
+ margin-left: var(--ode-spacing-2);
+}
+
+.tooltip--right::after {
+ content: '';
+ position: absolute;
+ right: 100%;
+ top: 50%;
+ transform: translateY(-50%);
+ border: var(--ode-spacing-2) solid transparent;
+ border-right-color: var(--ode-color-neutral-900);
+}
+
+[data-theme='dark'] .tooltip--right::after {
+ border-right-color: var(--ode-color-neutral-800);
+}
diff --git a/src/components/index.ts b/src/components/index.ts
new file mode 100644
index 0000000..29922c4
--- /dev/null
+++ b/src/components/index.ts
@@ -0,0 +1,18 @@
+/**
+ * ODE Design System Components
+ *
+ * Reusable components built with ODE design tokens.
+ * All components support dark mode and are fully accessible.
+ */
+
+export { default as Alert } from './Alert';
+export { default as Badge } from './Badge';
+export { default as Button } from './Button';
+export { default as ButtonGroup } from './ButtonGroup';
+export { default as Card } from './Card';
+export { default as CodeBlock } from './CodeBlock';
+export { default as Container } from './Container';
+export { default as Divider } from './Divider';
+export { default as Spacer } from './Spacer';
+export { default as Tag } from './Tag';
+export { default as Tooltip } from './Tooltip';