diff --git a/apps/engineering/content/design/components/form-input.mdx b/apps/engineering/content/design/components/form-input.mdx
new file mode 100644
index 0000000000..1ca274815b
--- /dev/null
+++ b/apps/engineering/content/design/components/form-input.mdx
@@ -0,0 +1,116 @@
+---
+title: FormInput
+description: A form input component with built-in label, description, and error handling capabilities.
+---
+import {
+ DefaultFormInputVariant,
+ RequiredFormInputVariant,
+ SuccessFormInputVariant,
+ WarningFormInputVariant,
+ ErrorFormInputVariant,
+ DisabledFormInputVariant,
+ DefaultValueFormInputVariant,
+ ReadonlyFormInputVariant,
+ ComplexFormInputVariant
+} from "./form/form-input.variants"
+
+# FormInput
+A comprehensive form input component that combines labels, descriptions, and validation states. Perfect for creating accessible, user-friendly forms with proper labeling and helpful context.
+
+## Default
+The default FormInput includes a label and optional description text, providing clear context for users.
+
+
+
+## Input States
+
+### Required Field
+Use the required prop to indicate mandatory fields. This automatically adds an asterisk (*) to the label.
+
+
+
+### Success State
+Indicates successful validation or acceptance of input value. The success icon and text provide positive feedback.
+
+
+
+### Warning State
+Used for potentially problematic inputs that don't prevent form submission. Includes a warning icon and explanatory text.
+
+
+
+### Error State
+Shows validation errors or other issues that need user attention. Features prominent error styling and message.
+
+
+
+### Disabled State
+Apply when the field should be non-interactive, such as during form submission or based on other field values.
+
+
+
+### With Default Value
+Pre-populated input with an initial value that users can modify.
+
+
+
+### Read-only State
+For displaying non-editable information while maintaining form layout consistency.
+
+
+
+## Complex Usage
+Example of a FormInput with multiple props configured for a specific use case.
+
+
+
+## Props
+The FormInput component extends the standard Input component props with additional form-specific properties:
+
+
+
+## Accessibility
+FormInput is built with accessibility in mind:
+- Labels are properly associated with inputs using htmlFor/id
+- Error messages are announced to screen readers using role="alert"
+- Required fields are marked both visually and via aria-required
+- Helper text is linked to inputs using aria-describedby
+- Error states are indicated using aria-invalid
+
+## Best Practices
+When using the FormInput component:
+- Always provide clear, concise labels
+- Use description text to provide additional context when needed
+- Keep error messages specific and actionable
+- Use required fields sparingly and logically
+- Group related FormInputs using fieldset and legend when appropriate
+- Consider the mobile experience when writing labels and descriptions
+- Maintain consistent validation patterns across your form
+- Use appropriate input types (email, tel, etc.) for better mobile keyboards
+- Consider character/word limits in descriptions and error messages
+- Test with screen readers to ensure accessibility
+
+## Layout Guidelines
+- Labels should be clear and concise
+- Error messages should appear immediately below the input
+- Description text should be helpful but not too lengthy
diff --git a/apps/engineering/content/design/components/form/form-input.variants.tsx b/apps/engineering/content/design/components/form/form-input.variants.tsx
new file mode 100644
index 0000000000..2c5aec329e
--- /dev/null
+++ b/apps/engineering/content/design/components/form/form-input.variants.tsx
@@ -0,0 +1,131 @@
+import { RenderComponentWithSnippet } from "@/app/components/render";
+import { FormInput } from "@unkey/ui";
+
+export const DefaultFormInputVariant = () => {
+ return (
+
+
+
+ );
+};
+
+// Required field variant
+export const RequiredFormInputVariant = () => {
+ return (
+
+
+
+ );
+};
+
+// Success variant
+export const SuccessFormInputVariant = () => {
+ return (
+
+
+
+ );
+};
+
+// Warning variant
+export const WarningFormInputVariant = () => {
+ return (
+
+
+
+ );
+};
+
+// Error variant
+export const ErrorFormInputVariant = () => {
+ return (
+
+
+
+ );
+};
+
+// Disabled variant
+export const DisabledFormInputVariant = () => {
+ return (
+
+
+
+ );
+};
+
+// With default value
+export const DefaultValueFormInputVariant = () => {
+ return (
+
+
+
+ );
+};
+
+// Readonly variant
+export const ReadonlyFormInputVariant = () => {
+ return (
+
+
+
+ );
+};
+
+// Complex example with multiple props
+export const ComplexFormInputVariant = () => {
+ return (
+
+
+
+ );
+};
diff --git a/apps/engineering/content/design/components/input.mdx b/apps/engineering/content/design/components/input.mdx
index 9edfe3b53b..a21e685d81 100644
--- a/apps/engineering/content/design/components/input.mdx
+++ b/apps/engineering/content/design/components/input.mdx
@@ -2,8 +2,6 @@
title: Input
description: A text input field component with different states, validations, and icon support.
---
-
-import { Input } from "@unkey/ui"
import { RenderComponentWithSnippet } from "@/app/components/render"
import {
InputDefaultVariant,
diff --git a/internal/ui/src/components/form/form-input.tsx b/internal/ui/src/components/form/form-input.tsx
new file mode 100644
index 0000000000..8733de2270
--- /dev/null
+++ b/internal/ui/src/components/form/form-input.tsx
@@ -0,0 +1,82 @@
+import { CircleInfo, TriangleWarning2 } from "@unkey/icons";
+import * as React from "react";
+import { cn } from "../../lib/utils";
+import { Input, type InputProps } from "../input";
+
+export interface FormInputProps extends InputProps {
+ label?: string;
+ description?: string;
+ required?: boolean;
+ error?: string;
+}
+
+export const FormInput = React.forwardRef(
+ ({ label, description, error, required, id, className, variant, ...props }, ref) => {
+ const inputVariant = error ? "error" : variant;
+
+ const inputId = id || React.useId();
+ const descriptionId = `${inputId}-helper`;
+ const errorId = `${inputId}-error`;
+
+ return (
+
+ );
+ },
+);
+
+FormInput.displayName = "FormInput";
diff --git a/internal/ui/src/components/form/index.tsx b/internal/ui/src/components/form/index.tsx
new file mode 100644
index 0000000000..d177cdb90a
--- /dev/null
+++ b/internal/ui/src/components/form/index.tsx
@@ -0,0 +1 @@
+export * from "./form-input";
diff --git a/internal/ui/src/components/input.tsx b/internal/ui/src/components/input.tsx
index 09a7d6d379..b692f54e02 100644
--- a/internal/ui/src/components/input.tsx
+++ b/internal/ui/src/components/input.tsx
@@ -13,19 +13,19 @@ const inputVariants = cva(
"[&:not(:placeholder-shown)]:focus:ring-0",
],
success: [
- "border border-success-6 hover:border-success-7 bg-gray-2",
+ "border border-success-9 hover:border-success-10 bg-gray-2",
"focus:border-success-8 focus:ring-2 focus:ring-success-2 focus-visible:outline-none",
- "[&:not(:placeholder-shown)]:focus:ring-success-3",
+ "[&:not(:placeholder-shown)]:focus:ring-success-0",
],
warning: [
- "border border-warning-6 hover:border-warning-7 bg-gray-2",
+ "border border-warning-9 hover:border-warning-10 bg-gray-2",
"focus:border-warning-8 focus:ring-2 focus:ring-warning-2 focus-visible:outline-none",
- "[&:not(:placeholder-shown)]:focus:ring-warning-3",
+ "[&:not(:placeholder-shown)]:focus:ring-warning-0",
],
error: [
- "border border-error-6 hover:border-error-7 bg-gray-2",
+ "border border-error-9 hover:border-error-10 bg-gray-2",
"focus:border-error-8 focus:ring-2 focus:ring-error-2 focus-visible:outline-none",
- "[&:not(:placeholder-shown)]:focus:ring-error-3",
+ "[&:not(:placeholder-shown)]:focus:ring-error-0",
],
},
},
diff --git a/internal/ui/src/index.ts b/internal/ui/src/index.ts
index 08defde55a..d1ad3cac3b 100644
--- a/internal/ui/src/index.ts
+++ b/internal/ui/src/index.ts
@@ -4,3 +4,4 @@ export * from "./components/tooltip";
export * from "./components/date-time/date-time";
export * from "./components/input";
export * from "./components/empty";
+export * from "./components/form";