diff --git a/docs/src/styles/components/field/basic.css b/docs/src/styles/components/field/basic.css
index e69de29..b0b72a2 100644
--- a/docs/src/styles/components/field/basic.css
+++ b/docs/src/styles/components/field/basic.css
@@ -0,0 +1,76 @@
+.field-basic [data-part="root"] {
+ display: flex;
+ flex-direction: column;
+ gap: 0.375rem;
+ width: 100%;
+ max-width: 20rem;
+ font-family: inherit;
+ color: var(--demo-neutral-fg);
+}
+
+.field-basic [data-part="label"] {
+ font-size: 0.875rem;
+ font-weight: 500;
+ line-height: 1.25rem;
+ color: var(--demo-neutral-fg);
+}
+
+.field-basic [data-part="input"],
+.field-basic [data-part="select"],
+.field-basic [data-part="textarea"] {
+ min-height: 2.25rem;
+ padding: 0.5rem 0.75rem;
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+ color: var(--demo-neutral-fg);
+ background: var(--demo-bg-checkbox);
+ border: 1px solid var(--demo-border-emphasized);
+ border-radius: 0.5rem;
+ outline: none;
+ transition: border-color 0.15s, box-shadow 0.15s;
+}
+
+.field-basic [data-part="input"]:focus,
+.field-basic [data-part="select"]:focus,
+.field-basic [data-part="textarea"]:focus {
+ border-color: var(--demo-coral-solid);
+ box-shadow: 0 0 0 2px var(--demo-coral-focus-ring);
+}
+
+.field-basic [data-part="input"]:is([data-disabled], [disabled]),
+.field-basic [data-part="select"]:is([data-disabled], [disabled]),
+.field-basic [data-part="textarea"]:is([data-disabled], [disabled]) {
+ cursor: not-allowed;
+ opacity: 0.5;
+ background: var(--demo-neutral-subtle);
+}
+
+.field-basic [data-part="select"] {
+ appearance: none;
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%2361605b' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
+ background-repeat: no-repeat;
+ background-position: right 0.625rem center;
+ padding-right: 2rem;
+}
+
+.field-basic [data-part="textarea"] {
+ min-height: 5rem;
+ resize: vertical;
+}
+
+.field-basic [data-part="helperText"] {
+ font-size: 0.75rem;
+ line-height: 1rem;
+ color: var(--demo-neutral-fg-muted);
+}
+
+.field-basic [data-part="errorText"] {
+ font-size: 0.75rem;
+ line-height: 1rem;
+ color: var(--demo-error);
+}
+
+.field-basic [data-part="requiredIndicator"] {
+ margin-left: 0.125rem;
+ color: var(--demo-error);
+}
diff --git a/docs/src/styles/components/field/disabled.css b/docs/src/styles/components/field/disabled.css
index e69de29..1314e8c 100644
--- a/docs/src/styles/components/field/disabled.css
+++ b/docs/src/styles/components/field/disabled.css
@@ -0,0 +1,76 @@
+.field-disabled [data-part="root"] {
+ display: flex;
+ flex-direction: column;
+ gap: 0.375rem;
+ width: 100%;
+ max-width: 20rem;
+ font-family: inherit;
+ color: var(--demo-neutral-fg);
+}
+
+.field-disabled [data-part="label"] {
+ font-size: 0.875rem;
+ font-weight: 500;
+ line-height: 1.25rem;
+ color: var(--demo-neutral-fg);
+}
+
+.field-disabled [data-part="input"],
+.field-disabled [data-part="select"],
+.field-disabled [data-part="textarea"] {
+ min-height: 2.25rem;
+ padding: 0.5rem 0.75rem;
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+ color: var(--demo-neutral-fg);
+ background: var(--demo-bg-checkbox);
+ border: 1px solid var(--demo-border-emphasized);
+ border-radius: 0.5rem;
+ outline: none;
+ transition: border-color 0.15s, box-shadow 0.15s;
+}
+
+.field-disabled [data-part="input"]:focus,
+.field-disabled [data-part="select"]:focus,
+.field-disabled [data-part="textarea"]:focus {
+ border-color: var(--demo-coral-solid);
+ box-shadow: 0 0 0 2px var(--demo-coral-focus-ring);
+}
+
+.field-disabled [data-part="input"]:is([data-disabled], [disabled]),
+.field-disabled [data-part="select"]:is([data-disabled], [disabled]),
+.field-disabled [data-part="textarea"]:is([data-disabled], [disabled]) {
+ cursor: not-allowed;
+ opacity: 0.5;
+ background: var(--demo-neutral-subtle);
+}
+
+.field-disabled [data-part="select"] {
+ appearance: none;
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%2361605b' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
+ background-repeat: no-repeat;
+ background-position: right 0.625rem center;
+ padding-right: 2rem;
+}
+
+.field-disabled [data-part="textarea"] {
+ min-height: 5rem;
+ resize: vertical;
+}
+
+.field-disabled [data-part="helperText"] {
+ font-size: 0.75rem;
+ line-height: 1rem;
+ color: var(--demo-neutral-fg-muted);
+}
+
+.field-disabled [data-part="errorText"] {
+ font-size: 0.75rem;
+ line-height: 1rem;
+ color: var(--demo-error);
+}
+
+.field-disabled [data-part="requiredIndicator"] {
+ margin-left: 0.125rem;
+ color: var(--demo-error);
+}
diff --git a/docs/src/styles/components/field/input-controlled.css b/docs/src/styles/components/field/input-controlled.css
index e69de29..7d2cd3a 100644
--- a/docs/src/styles/components/field/input-controlled.css
+++ b/docs/src/styles/components/field/input-controlled.css
@@ -0,0 +1,76 @@
+.field-inputcontrolled [data-part="root"] {
+ display: flex;
+ flex-direction: column;
+ gap: 0.375rem;
+ width: 100%;
+ max-width: 20rem;
+ font-family: inherit;
+ color: var(--demo-neutral-fg);
+}
+
+.field-inputcontrolled [data-part="label"] {
+ font-size: 0.875rem;
+ font-weight: 500;
+ line-height: 1.25rem;
+ color: var(--demo-neutral-fg);
+}
+
+.field-inputcontrolled [data-part="input"],
+.field-inputcontrolled [data-part="select"],
+.field-inputcontrolled [data-part="textarea"] {
+ min-height: 2.25rem;
+ padding: 0.5rem 0.75rem;
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+ color: var(--demo-neutral-fg);
+ background: var(--demo-bg-checkbox);
+ border: 1px solid var(--demo-border-emphasized);
+ border-radius: 0.5rem;
+ outline: none;
+ transition: border-color 0.15s, box-shadow 0.15s;
+}
+
+.field-inputcontrolled [data-part="input"]:focus,
+.field-inputcontrolled [data-part="select"]:focus,
+.field-inputcontrolled [data-part="textarea"]:focus {
+ border-color: var(--demo-coral-solid);
+ box-shadow: 0 0 0 2px var(--demo-coral-focus-ring);
+}
+
+.field-inputcontrolled [data-part="input"]:is([data-disabled], [disabled]),
+.field-inputcontrolled [data-part="select"]:is([data-disabled], [disabled]),
+.field-inputcontrolled [data-part="textarea"]:is([data-disabled], [disabled]) {
+ cursor: not-allowed;
+ opacity: 0.5;
+ background: var(--demo-neutral-subtle);
+}
+
+.field-inputcontrolled [data-part="select"] {
+ appearance: none;
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%2361605b' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
+ background-repeat: no-repeat;
+ background-position: right 0.625rem center;
+ padding-right: 2rem;
+}
+
+.field-inputcontrolled [data-part="textarea"] {
+ min-height: 5rem;
+ resize: vertical;
+}
+
+.field-inputcontrolled [data-part="helperText"] {
+ font-size: 0.75rem;
+ line-height: 1rem;
+ color: var(--demo-neutral-fg-muted);
+}
+
+.field-inputcontrolled [data-part="errorText"] {
+ font-size: 0.75rem;
+ line-height: 1rem;
+ color: var(--demo-error);
+}
+
+.field-inputcontrolled [data-part="requiredIndicator"] {
+ margin-left: 0.125rem;
+ color: var(--demo-error);
+}
diff --git a/docs/src/styles/components/field/input.css b/docs/src/styles/components/field/input.css
index e69de29..955a8a1 100644
--- a/docs/src/styles/components/field/input.css
+++ b/docs/src/styles/components/field/input.css
@@ -0,0 +1,76 @@
+.field-input [data-part="root"] {
+ display: flex;
+ flex-direction: column;
+ gap: 0.375rem;
+ width: 100%;
+ max-width: 20rem;
+ font-family: inherit;
+ color: var(--demo-neutral-fg);
+}
+
+.field-input [data-part="label"] {
+ font-size: 0.875rem;
+ font-weight: 500;
+ line-height: 1.25rem;
+ color: var(--demo-neutral-fg);
+}
+
+.field-input [data-part="input"],
+.field-input [data-part="select"],
+.field-input [data-part="textarea"] {
+ min-height: 2.25rem;
+ padding: 0.5rem 0.75rem;
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+ color: var(--demo-neutral-fg);
+ background: var(--demo-bg-checkbox);
+ border: 1px solid var(--demo-border-emphasized);
+ border-radius: 0.5rem;
+ outline: none;
+ transition: border-color 0.15s, box-shadow 0.15s;
+}
+
+.field-input [data-part="input"]:focus,
+.field-input [data-part="select"]:focus,
+.field-input [data-part="textarea"]:focus {
+ border-color: var(--demo-coral-solid);
+ box-shadow: 0 0 0 2px var(--demo-coral-focus-ring);
+}
+
+.field-input [data-part="input"]:is([data-disabled], [disabled]),
+.field-input [data-part="select"]:is([data-disabled], [disabled]),
+.field-input [data-part="textarea"]:is([data-disabled], [disabled]) {
+ cursor: not-allowed;
+ opacity: 0.5;
+ background: var(--demo-neutral-subtle);
+}
+
+.field-input [data-part="select"] {
+ appearance: none;
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%2361605b' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
+ background-repeat: no-repeat;
+ background-position: right 0.625rem center;
+ padding-right: 2rem;
+}
+
+.field-input [data-part="textarea"] {
+ min-height: 5rem;
+ resize: vertical;
+}
+
+.field-input [data-part="helperText"] {
+ font-size: 0.75rem;
+ line-height: 1rem;
+ color: var(--demo-neutral-fg-muted);
+}
+
+.field-input [data-part="errorText"] {
+ font-size: 0.75rem;
+ line-height: 1rem;
+ color: var(--demo-error);
+}
+
+.field-input [data-part="requiredIndicator"] {
+ margin-left: 0.125rem;
+ color: var(--demo-error);
+}
diff --git a/docs/src/styles/components/field/reactive-invalid.css b/docs/src/styles/components/field/reactive-invalid.css
index e69de29..41cd35e 100644
--- a/docs/src/styles/components/field/reactive-invalid.css
+++ b/docs/src/styles/components/field/reactive-invalid.css
@@ -0,0 +1,95 @@
+.field-reactiveinvalid [data-part="root"] {
+ display: flex;
+ flex-direction: column;
+ gap: 0.375rem;
+ width: 100%;
+ max-width: 20rem;
+ font-family: inherit;
+ color: var(--demo-neutral-fg);
+}
+
+.field-reactiveinvalid [data-part="label"] {
+ font-size: 0.875rem;
+ font-weight: 500;
+ line-height: 1.25rem;
+ color: var(--demo-neutral-fg);
+}
+
+.field-reactiveinvalid [data-part="input"],
+.field-reactiveinvalid [data-part="select"],
+.field-reactiveinvalid [data-part="textarea"] {
+ min-height: 2.25rem;
+ padding: 0.5rem 0.75rem;
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+ color: var(--demo-neutral-fg);
+ background: var(--demo-bg-checkbox);
+ border: 1px solid var(--demo-border-emphasized);
+ border-radius: 0.5rem;
+ outline: none;
+ transition: border-color 0.15s, box-shadow 0.15s;
+}
+
+.field-reactiveinvalid [data-part="input"]:focus,
+.field-reactiveinvalid [data-part="select"]:focus,
+.field-reactiveinvalid [data-part="textarea"]:focus {
+ border-color: var(--demo-coral-solid);
+ box-shadow: 0 0 0 2px var(--demo-coral-focus-ring);
+}
+
+.field-reactiveinvalid [data-part="input"]:is([data-disabled], [disabled]),
+.field-reactiveinvalid [data-part="select"]:is([data-disabled], [disabled]),
+.field-reactiveinvalid [data-part="textarea"]:is([data-disabled], [disabled]) {
+ cursor: not-allowed;
+ opacity: 0.5;
+ background: var(--demo-neutral-subtle);
+}
+
+.field-reactiveinvalid [data-part="select"] {
+ appearance: none;
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%2361605b' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
+ background-repeat: no-repeat;
+ background-position: right 0.625rem center;
+ padding-right: 2rem;
+}
+
+.field-reactiveinvalid [data-part="textarea"] {
+ min-height: 5rem;
+ resize: vertical;
+}
+
+.field-reactiveinvalid [data-part="helperText"] {
+ font-size: 0.75rem;
+ line-height: 1rem;
+ color: var(--demo-neutral-fg-muted);
+}
+
+.field-reactiveinvalid [data-part="errorText"] {
+ font-size: 0.75rem;
+ line-height: 1rem;
+ color: var(--demo-error);
+}
+
+.field-reactiveinvalid [data-part="requiredIndicator"] {
+ margin-left: 0.125rem;
+ color: var(--demo-error);
+}
+
+.field-reactiveinvalid input:not([data-part]) {
+ min-height: 2.25rem;
+ padding: 0.5rem 0.75rem;
+ margin-bottom: 0.75rem;
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+ color: var(--demo-neutral-fg);
+ background: var(--demo-bg-checkbox);
+ border: 1px solid var(--demo-border-emphasized);
+ border-radius: 0.5rem;
+ outline: none;
+ transition: border-color 0.15s, box-shadow 0.15s;
+}
+
+.field-reactiveinvalid input:not([data-part]):focus {
+ border-color: var(--demo-coral-solid);
+ box-shadow: 0 0 0 2px var(--demo-coral-focus-ring);
+}
diff --git a/docs/src/styles/components/field/required-indicator.css b/docs/src/styles/components/field/required-indicator.css
index e69de29..be283b9 100644
--- a/docs/src/styles/components/field/required-indicator.css
+++ b/docs/src/styles/components/field/required-indicator.css
@@ -0,0 +1,76 @@
+.field-requiredindicator [data-part="root"] {
+ display: flex;
+ flex-direction: column;
+ gap: 0.375rem;
+ width: 100%;
+ max-width: 20rem;
+ font-family: inherit;
+ color: var(--demo-neutral-fg);
+}
+
+.field-requiredindicator [data-part="label"] {
+ font-size: 0.875rem;
+ font-weight: 500;
+ line-height: 1.25rem;
+ color: var(--demo-neutral-fg);
+}
+
+.field-requiredindicator [data-part="input"],
+.field-requiredindicator [data-part="select"],
+.field-requiredindicator [data-part="textarea"] {
+ min-height: 2.25rem;
+ padding: 0.5rem 0.75rem;
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+ color: var(--demo-neutral-fg);
+ background: var(--demo-bg-checkbox);
+ border: 1px solid var(--demo-border-emphasized);
+ border-radius: 0.5rem;
+ outline: none;
+ transition: border-color 0.15s, box-shadow 0.15s;
+}
+
+.field-requiredindicator [data-part="input"]:focus,
+.field-requiredindicator [data-part="select"]:focus,
+.field-requiredindicator [data-part="textarea"]:focus {
+ border-color: var(--demo-coral-solid);
+ box-shadow: 0 0 0 2px var(--demo-coral-focus-ring);
+}
+
+.field-requiredindicator [data-part="input"]:is([data-disabled], [disabled]),
+.field-requiredindicator [data-part="select"]:is([data-disabled], [disabled]),
+.field-requiredindicator [data-part="textarea"]:is([data-disabled], [disabled]) {
+ cursor: not-allowed;
+ opacity: 0.5;
+ background: var(--demo-neutral-subtle);
+}
+
+.field-requiredindicator [data-part="select"] {
+ appearance: none;
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%2361605b' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
+ background-repeat: no-repeat;
+ background-position: right 0.625rem center;
+ padding-right: 2rem;
+}
+
+.field-requiredindicator [data-part="textarea"] {
+ min-height: 5rem;
+ resize: vertical;
+}
+
+.field-requiredindicator [data-part="helperText"] {
+ font-size: 0.75rem;
+ line-height: 1rem;
+ color: var(--demo-neutral-fg-muted);
+}
+
+.field-requiredindicator [data-part="errorText"] {
+ font-size: 0.75rem;
+ line-height: 1rem;
+ color: var(--demo-error);
+}
+
+.field-requiredindicator [data-part="requiredIndicator"] {
+ margin-left: 0.125rem;
+ color: var(--demo-error);
+}
diff --git a/docs/src/styles/components/field/select-controlled.css b/docs/src/styles/components/field/select-controlled.css
index e69de29..23a5272 100644
--- a/docs/src/styles/components/field/select-controlled.css
+++ b/docs/src/styles/components/field/select-controlled.css
@@ -0,0 +1,76 @@
+.field-selectcontrolled [data-part="root"] {
+ display: flex;
+ flex-direction: column;
+ gap: 0.375rem;
+ width: 100%;
+ max-width: 20rem;
+ font-family: inherit;
+ color: var(--demo-neutral-fg);
+}
+
+.field-selectcontrolled [data-part="label"] {
+ font-size: 0.875rem;
+ font-weight: 500;
+ line-height: 1.25rem;
+ color: var(--demo-neutral-fg);
+}
+
+.field-selectcontrolled [data-part="input"],
+.field-selectcontrolled [data-part="select"],
+.field-selectcontrolled [data-part="textarea"] {
+ min-height: 2.25rem;
+ padding: 0.5rem 0.75rem;
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+ color: var(--demo-neutral-fg);
+ background: var(--demo-bg-checkbox);
+ border: 1px solid var(--demo-border-emphasized);
+ border-radius: 0.5rem;
+ outline: none;
+ transition: border-color 0.15s, box-shadow 0.15s;
+}
+
+.field-selectcontrolled [data-part="input"]:focus,
+.field-selectcontrolled [data-part="select"]:focus,
+.field-selectcontrolled [data-part="textarea"]:focus {
+ border-color: var(--demo-coral-solid);
+ box-shadow: 0 0 0 2px var(--demo-coral-focus-ring);
+}
+
+.field-selectcontrolled [data-part="input"]:is([data-disabled], [disabled]),
+.field-selectcontrolled [data-part="select"]:is([data-disabled], [disabled]),
+.field-selectcontrolled [data-part="textarea"]:is([data-disabled], [disabled]) {
+ cursor: not-allowed;
+ opacity: 0.5;
+ background: var(--demo-neutral-subtle);
+}
+
+.field-selectcontrolled [data-part="select"] {
+ appearance: none;
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%2361605b' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
+ background-repeat: no-repeat;
+ background-position: right 0.625rem center;
+ padding-right: 2rem;
+}
+
+.field-selectcontrolled [data-part="textarea"] {
+ min-height: 5rem;
+ resize: vertical;
+}
+
+.field-selectcontrolled [data-part="helperText"] {
+ font-size: 0.75rem;
+ line-height: 1rem;
+ color: var(--demo-neutral-fg-muted);
+}
+
+.field-selectcontrolled [data-part="errorText"] {
+ font-size: 0.75rem;
+ line-height: 1rem;
+ color: var(--demo-error);
+}
+
+.field-selectcontrolled [data-part="requiredIndicator"] {
+ margin-left: 0.125rem;
+ color: var(--demo-error);
+}
diff --git a/docs/src/styles/components/field/select.css b/docs/src/styles/components/field/select.css
index e69de29..7bb7203 100644
--- a/docs/src/styles/components/field/select.css
+++ b/docs/src/styles/components/field/select.css
@@ -0,0 +1,76 @@
+.field-select [data-part="root"] {
+ display: flex;
+ flex-direction: column;
+ gap: 0.375rem;
+ width: 100%;
+ max-width: 20rem;
+ font-family: inherit;
+ color: var(--demo-neutral-fg);
+}
+
+.field-select [data-part="label"] {
+ font-size: 0.875rem;
+ font-weight: 500;
+ line-height: 1.25rem;
+ color: var(--demo-neutral-fg);
+}
+
+.field-select [data-part="input"],
+.field-select [data-part="select"],
+.field-select [data-part="textarea"] {
+ min-height: 2.25rem;
+ padding: 0.5rem 0.75rem;
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+ color: var(--demo-neutral-fg);
+ background: var(--demo-bg-checkbox);
+ border: 1px solid var(--demo-border-emphasized);
+ border-radius: 0.5rem;
+ outline: none;
+ transition: border-color 0.15s, box-shadow 0.15s;
+}
+
+.field-select [data-part="input"]:focus,
+.field-select [data-part="select"]:focus,
+.field-select [data-part="textarea"]:focus {
+ border-color: var(--demo-coral-solid);
+ box-shadow: 0 0 0 2px var(--demo-coral-focus-ring);
+}
+
+.field-select [data-part="input"]:is([data-disabled], [disabled]),
+.field-select [data-part="select"]:is([data-disabled], [disabled]),
+.field-select [data-part="textarea"]:is([data-disabled], [disabled]) {
+ cursor: not-allowed;
+ opacity: 0.5;
+ background: var(--demo-neutral-subtle);
+}
+
+.field-select [data-part="select"] {
+ appearance: none;
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%2361605b' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
+ background-repeat: no-repeat;
+ background-position: right 0.625rem center;
+ padding-right: 2rem;
+}
+
+.field-select [data-part="textarea"] {
+ min-height: 5rem;
+ resize: vertical;
+}
+
+.field-select [data-part="helperText"] {
+ font-size: 0.75rem;
+ line-height: 1rem;
+ color: var(--demo-neutral-fg-muted);
+}
+
+.field-select [data-part="errorText"] {
+ font-size: 0.75rem;
+ line-height: 1rem;
+ color: var(--demo-error);
+}
+
+.field-select [data-part="requiredIndicator"] {
+ margin-left: 0.125rem;
+ color: var(--demo-error);
+}
diff --git a/docs/src/styles/components/field/textarea-autoresize.css b/docs/src/styles/components/field/textarea-autoresize.css
index e69de29..4365ad5 100644
--- a/docs/src/styles/components/field/textarea-autoresize.css
+++ b/docs/src/styles/components/field/textarea-autoresize.css
@@ -0,0 +1,76 @@
+.field-textareaautoresize [data-part="root"] {
+ display: flex;
+ flex-direction: column;
+ gap: 0.375rem;
+ width: 100%;
+ max-width: 20rem;
+ font-family: inherit;
+ color: var(--demo-neutral-fg);
+}
+
+.field-textareaautoresize [data-part="label"] {
+ font-size: 0.875rem;
+ font-weight: 500;
+ line-height: 1.25rem;
+ color: var(--demo-neutral-fg);
+}
+
+.field-textareaautoresize [data-part="input"],
+.field-textareaautoresize [data-part="select"],
+.field-textareaautoresize [data-part="textarea"] {
+ min-height: 2.25rem;
+ padding: 0.5rem 0.75rem;
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+ color: var(--demo-neutral-fg);
+ background: var(--demo-bg-checkbox);
+ border: 1px solid var(--demo-border-emphasized);
+ border-radius: 0.5rem;
+ outline: none;
+ transition: border-color 0.15s, box-shadow 0.15s;
+}
+
+.field-textareaautoresize [data-part="input"]:focus,
+.field-textareaautoresize [data-part="select"]:focus,
+.field-textareaautoresize [data-part="textarea"]:focus {
+ border-color: var(--demo-coral-solid);
+ box-shadow: 0 0 0 2px var(--demo-coral-focus-ring);
+}
+
+.field-textareaautoresize [data-part="input"]:is([data-disabled], [disabled]),
+.field-textareaautoresize [data-part="select"]:is([data-disabled], [disabled]),
+.field-textareaautoresize [data-part="textarea"]:is([data-disabled], [disabled]) {
+ cursor: not-allowed;
+ opacity: 0.5;
+ background: var(--demo-neutral-subtle);
+}
+
+.field-textareaautoresize [data-part="select"] {
+ appearance: none;
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%2361605b' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
+ background-repeat: no-repeat;
+ background-position: right 0.625rem center;
+ padding-right: 2rem;
+}
+
+.field-textareaautoresize [data-part="textarea"] {
+ min-height: 5rem;
+ resize: vertical;
+}
+
+.field-textareaautoresize [data-part="helperText"] {
+ font-size: 0.75rem;
+ line-height: 1rem;
+ color: var(--demo-neutral-fg-muted);
+}
+
+.field-textareaautoresize [data-part="errorText"] {
+ font-size: 0.75rem;
+ line-height: 1rem;
+ color: var(--demo-error);
+}
+
+.field-textareaautoresize [data-part="requiredIndicator"] {
+ margin-left: 0.125rem;
+ color: var(--demo-error);
+}
diff --git a/docs/src/styles/components/field/textarea-controlled.css b/docs/src/styles/components/field/textarea-controlled.css
index e69de29..0e1da9b 100644
--- a/docs/src/styles/components/field/textarea-controlled.css
+++ b/docs/src/styles/components/field/textarea-controlled.css
@@ -0,0 +1,76 @@
+.field-textareacontrolled [data-part="root"] {
+ display: flex;
+ flex-direction: column;
+ gap: 0.375rem;
+ width: 100%;
+ max-width: 20rem;
+ font-family: inherit;
+ color: var(--demo-neutral-fg);
+}
+
+.field-textareacontrolled [data-part="label"] {
+ font-size: 0.875rem;
+ font-weight: 500;
+ line-height: 1.25rem;
+ color: var(--demo-neutral-fg);
+}
+
+.field-textareacontrolled [data-part="input"],
+.field-textareacontrolled [data-part="select"],
+.field-textareacontrolled [data-part="textarea"] {
+ min-height: 2.25rem;
+ padding: 0.5rem 0.75rem;
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+ color: var(--demo-neutral-fg);
+ background: var(--demo-bg-checkbox);
+ border: 1px solid var(--demo-border-emphasized);
+ border-radius: 0.5rem;
+ outline: none;
+ transition: border-color 0.15s, box-shadow 0.15s;
+}
+
+.field-textareacontrolled [data-part="input"]:focus,
+.field-textareacontrolled [data-part="select"]:focus,
+.field-textareacontrolled [data-part="textarea"]:focus {
+ border-color: var(--demo-coral-solid);
+ box-shadow: 0 0 0 2px var(--demo-coral-focus-ring);
+}
+
+.field-textareacontrolled [data-part="input"]:is([data-disabled], [disabled]),
+.field-textareacontrolled [data-part="select"]:is([data-disabled], [disabled]),
+.field-textareacontrolled [data-part="textarea"]:is([data-disabled], [disabled]) {
+ cursor: not-allowed;
+ opacity: 0.5;
+ background: var(--demo-neutral-subtle);
+}
+
+.field-textareacontrolled [data-part="select"] {
+ appearance: none;
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%2361605b' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
+ background-repeat: no-repeat;
+ background-position: right 0.625rem center;
+ padding-right: 2rem;
+}
+
+.field-textareacontrolled [data-part="textarea"] {
+ min-height: 5rem;
+ resize: vertical;
+}
+
+.field-textareacontrolled [data-part="helperText"] {
+ font-size: 0.75rem;
+ line-height: 1rem;
+ color: var(--demo-neutral-fg-muted);
+}
+
+.field-textareacontrolled [data-part="errorText"] {
+ font-size: 0.75rem;
+ line-height: 1rem;
+ color: var(--demo-error);
+}
+
+.field-textareacontrolled [data-part="requiredIndicator"] {
+ margin-left: 0.125rem;
+ color: var(--demo-error);
+}
diff --git a/docs/src/styles/components/field/textarea.css b/docs/src/styles/components/field/textarea.css
index e69de29..7380b47 100644
--- a/docs/src/styles/components/field/textarea.css
+++ b/docs/src/styles/components/field/textarea.css
@@ -0,0 +1,76 @@
+.field-textarea [data-part="root"] {
+ display: flex;
+ flex-direction: column;
+ gap: 0.375rem;
+ width: 100%;
+ max-width: 20rem;
+ font-family: inherit;
+ color: var(--demo-neutral-fg);
+}
+
+.field-textarea [data-part="label"] {
+ font-size: 0.875rem;
+ font-weight: 500;
+ line-height: 1.25rem;
+ color: var(--demo-neutral-fg);
+}
+
+.field-textarea [data-part="input"],
+.field-textarea [data-part="select"],
+.field-textarea [data-part="textarea"] {
+ min-height: 2.25rem;
+ padding: 0.5rem 0.75rem;
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+ color: var(--demo-neutral-fg);
+ background: var(--demo-bg-checkbox);
+ border: 1px solid var(--demo-border-emphasized);
+ border-radius: 0.5rem;
+ outline: none;
+ transition: border-color 0.15s, box-shadow 0.15s;
+}
+
+.field-textarea [data-part="input"]:focus,
+.field-textarea [data-part="select"]:focus,
+.field-textarea [data-part="textarea"]:focus {
+ border-color: var(--demo-coral-solid);
+ box-shadow: 0 0 0 2px var(--demo-coral-focus-ring);
+}
+
+.field-textarea [data-part="input"]:is([data-disabled], [disabled]),
+.field-textarea [data-part="select"]:is([data-disabled], [disabled]),
+.field-textarea [data-part="textarea"]:is([data-disabled], [disabled]) {
+ cursor: not-allowed;
+ opacity: 0.5;
+ background: var(--demo-neutral-subtle);
+}
+
+.field-textarea [data-part="select"] {
+ appearance: none;
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%2361605b' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
+ background-repeat: no-repeat;
+ background-position: right 0.625rem center;
+ padding-right: 2rem;
+}
+
+.field-textarea [data-part="textarea"] {
+ min-height: 5rem;
+ resize: vertical;
+}
+
+.field-textarea [data-part="helperText"] {
+ font-size: 0.75rem;
+ line-height: 1rem;
+ color: var(--demo-neutral-fg-muted);
+}
+
+.field-textarea [data-part="errorText"] {
+ font-size: 0.75rem;
+ line-height: 1rem;
+ color: var(--demo-error);
+}
+
+.field-textarea [data-part="requiredIndicator"] {
+ margin-left: 0.125rem;
+ color: var(--demo-error);
+}
diff --git a/packages/react/src/components/field/examples/Input.tsx b/packages/react/src/components/field/examples/Input.tsx
new file mode 100644
index 0000000..b339a80
--- /dev/null
+++ b/packages/react/src/components/field/examples/Input.tsx
@@ -0,0 +1,14 @@
+import { Field } from '../index'
+
+export interface InputProps extends Field.RootProps {}
+
+export function Input(props: InputProps) {
+ return (
+
+ Label
+
+ Some additional Info
+ Error Info
+
+ )
+}
diff --git a/packages/react/src/components/field/examples/InputControlled.tsx b/packages/react/src/components/field/examples/InputControlled.tsx
new file mode 100644
index 0000000..ebc3cbd
--- /dev/null
+++ b/packages/react/src/components/field/examples/InputControlled.tsx
@@ -0,0 +1,20 @@
+import { useState } from 'react'
+import { Field } from '../index'
+
+export interface InputControlledProps extends Field.RootProps {}
+
+export function InputControlled(props: InputControlledProps) {
+ const [value, setValue] = useState('Input is controlled')
+
+ return (
+ <>
+ Input text: {value}
+
+ Label
+ setValue(e.target.value)} />
+ Some additional Info
+ Error Info
+
+ >
+ )
+}
diff --git a/packages/react/src/components/field/examples/ReactiveInvalid.tsx b/packages/react/src/components/field/examples/ReactiveInvalid.tsx
new file mode 100644
index 0000000..960a3b6
--- /dev/null
+++ b/packages/react/src/components/field/examples/ReactiveInvalid.tsx
@@ -0,0 +1,23 @@
+import { useState } from 'react'
+import { Field } from '../index'
+
+export interface ReactiveInvalidProps extends Field.RootProps {}
+
+export function ReactiveInvalid(props: ReactiveInvalidProps) {
+ const [errorMessage, setErrorMessage] = useState('')
+ const invalid = !!errorMessage
+
+ return (
+ <>
+ setErrorMessage(e.target.value)}
+ placeholder="Type an error message"
+ />
+
+ IsInvalid? {String(invalid)}
+ {errorMessage}
+
+ >
+ )
+}
diff --git a/packages/react/src/components/field/examples/RequiredIndicator.tsx b/packages/react/src/components/field/examples/RequiredIndicator.tsx
new file mode 100644
index 0000000..8db310c
--- /dev/null
+++ b/packages/react/src/components/field/examples/RequiredIndicator.tsx
@@ -0,0 +1,15 @@
+import { Field } from '../index'
+
+export interface RequiredIndicatorProps extends Field.RootProps {}
+
+export function RequiredIndicator(props: RequiredIndicatorProps) {
+ return (
+
+
+ Username
+
+
+
+
+ )
+}
diff --git a/packages/react/src/components/field/examples/Select.tsx b/packages/react/src/components/field/examples/Select.tsx
new file mode 100644
index 0000000..5212380
--- /dev/null
+++ b/packages/react/src/components/field/examples/Select.tsx
@@ -0,0 +1,18 @@
+import { Field } from '../index'
+
+export interface SelectProps extends Field.RootProps {}
+
+export function Select(props: SelectProps) {
+ return (
+
+ Label
+
+
+
+
+
+ Some additional Info
+ Error Info
+
+ )
+}
diff --git a/packages/react/src/components/field/examples/SelectControlled.tsx b/packages/react/src/components/field/examples/SelectControlled.tsx
new file mode 100644
index 0000000..460c9d9
--- /dev/null
+++ b/packages/react/src/components/field/examples/SelectControlled.tsx
@@ -0,0 +1,24 @@
+import { useState } from 'react'
+import { Field } from '../index'
+
+export interface SelectControlledProps extends Field.RootProps {}
+
+export function SelectControlled(props: SelectControlledProps) {
+ const [value, setValue] = useState('3')
+
+ return (
+ <>
+ Selected: Option {value}
+
+ Label
+ setValue(e.target.value)}>
+
+
+
+
+ Some additional Info
+ Error Info
+
+ >
+ )
+}
diff --git a/packages/react/src/components/field/examples/Textarea.tsx b/packages/react/src/components/field/examples/Textarea.tsx
new file mode 100644
index 0000000..74f5a0b
--- /dev/null
+++ b/packages/react/src/components/field/examples/Textarea.tsx
@@ -0,0 +1,14 @@
+import { Field } from '../index'
+
+export interface TextareaProps extends Field.RootProps {}
+
+export function Textarea(props: TextareaProps) {
+ return (
+
+ Label
+
+ Some additional Info
+ Error Info
+
+ )
+}
diff --git a/packages/react/src/components/field/examples/TextareaAutoresize.tsx b/packages/react/src/components/field/examples/TextareaAutoresize.tsx
new file mode 100644
index 0000000..ce184e5
--- /dev/null
+++ b/packages/react/src/components/field/examples/TextareaAutoresize.tsx
@@ -0,0 +1,14 @@
+import { Field } from '../index'
+
+export interface TextareaAutoresizeProps extends Field.RootProps {}
+
+export function TextareaAutoresize(props: TextareaAutoresizeProps) {
+ return (
+
+ Label
+
+ Some additional Info
+ Error Info
+
+ )
+}
diff --git a/packages/react/src/components/field/examples/TextareaControlled.tsx b/packages/react/src/components/field/examples/TextareaControlled.tsx
new file mode 100644
index 0000000..dfd2374
--- /dev/null
+++ b/packages/react/src/components/field/examples/TextareaControlled.tsx
@@ -0,0 +1,20 @@
+import { useState } from 'react'
+import { Field } from '../index'
+
+export interface TextareaControlledProps extends Field.RootProps {}
+
+export function TextareaControlled(props: TextareaControlledProps) {
+ const [value, setValue] = useState('This is some text\nthen more text')
+
+ return (
+ <>
+ Textarea value: {value}
+
+ Label
+ setValue(e.target.value)} />
+ Some additional Info
+ Error Info
+
+ >
+ )
+}
diff --git a/packages/react/src/components/field/stories/field.stories.tsx b/packages/react/src/components/field/stories/field.stories.tsx
index efc5050..f8bdec5 100644
--- a/packages/react/src/components/field/stories/field.stories.tsx
+++ b/packages/react/src/components/field/stories/field.stories.tsx
@@ -9,3 +9,12 @@ export default meta
export { Basic } from '../examples/Basic'
export { Disabled } from '../examples/Disabled'
+export { Input } from '../examples/Input'
+export { InputControlled } from '../examples/InputControlled'
+export { ReactiveInvalid } from '../examples/ReactiveInvalid'
+export { RequiredIndicator } from '../examples/RequiredIndicator'
+export { Select } from '../examples/Select'
+export { SelectControlled } from '../examples/SelectControlled'
+export { Textarea } from '../examples/Textarea'
+export { TextareaAutoresize } from '../examples/TextareaAutoresize'
+export { TextareaControlled } from '../examples/TextareaControlled'
diff --git a/packages/vue/src/components/calendar/examples/Basic.vue b/packages/vue/src/components/calendar/examples/Basic.vue
index 3555454..98d5de3 100644
--- a/packages/vue/src/components/calendar/examples/Basic.vue
+++ b/packages/vue/src/components/calendar/examples/Basic.vue
@@ -9,6 +9,8 @@ import { Calendar } from '../index'
Clear
+
+
Last 7 days
diff --git a/packages/vue/src/components/field/anatomy.ts b/packages/vue/src/components/field/anatomy.ts
index 7f47531..e74d95c 100644
--- a/packages/vue/src/components/field/anatomy.ts
+++ b/packages/vue/src/components/field/anatomy.ts
@@ -6,6 +6,8 @@ export const fieldAnatomy = createAnatomy('field').parts(
'helperText',
'input',
'label',
+ 'select',
+ 'textarea',
'requiredIndicator',
)
export const parts = fieldAnatomy.build()
diff --git a/packages/vue/src/components/field/components/Textarea.vue b/packages/vue/src/components/field/components/Textarea.vue
index 397f4f3..7cd0a24 100644
--- a/packages/vue/src/components/field/components/Textarea.vue
+++ b/packages/vue/src/components/field/components/Textarea.vue
@@ -33,12 +33,17 @@ const props = defineProps()
const field = useFieldContext()
const emit = defineEmits(['update:modelValue'])
+function setTextareaRef(node: Element | null) {
+ if (props.autoresize && node instanceof HTMLTextAreaElement)
+ autoresizeTextarea(node)
+}
+
useForwardExpose()
emit('update:modelValue', (event.target as HTMLTextAreaElement).value)"
diff --git a/packages/vue/src/components/field/examples/AllParts.vue b/packages/vue/src/components/field/examples/AllParts.vue
new file mode 100644
index 0000000..911e133
--- /dev/null
+++ b/packages/vue/src/components/field/examples/AllParts.vue
@@ -0,0 +1,23 @@
+
+
+
+
+
+ Label
+
+
+
+
+
+
+
+ Some additional Info
+ Error Info
+
+
diff --git a/packages/vue/src/components/field/examples/TextareaControlled.vue b/packages/vue/src/components/field/examples/TextareaControlled.vue
index 13fc727..28d3566 100644
--- a/packages/vue/src/components/field/examples/TextareaControlled.vue
+++ b/packages/vue/src/components/field/examples/TextareaControlled.vue
@@ -2,7 +2,7 @@
import { Field } from '../index'
import { ref } from 'vue'
-const model = ref(['This is some text', 'then more text'])
+const model = ref('This is some text\nthen more text')
diff --git a/packages/vue/src/components/field/stories/field.stories.ts b/packages/vue/src/components/field/stories/field.stories.ts
index d73be7a..ab9021b 100644
--- a/packages/vue/src/components/field/stories/field.stories.ts
+++ b/packages/vue/src/components/field/stories/field.stories.ts
@@ -1,4 +1,14 @@
import Basic from '../examples/Basic.vue'
+import Disabled from '../examples/Disabled.vue'
+import Input from '../examples/Input.vue'
+import InputControlled from '../examples/InputControlled.vue'
+import ReactiveInvalid from '../examples/ReactiveInvalid.vue'
+import RequiredIndicator from '../examples/RequiredIndicator.vue'
+import Select from '../examples/Select.vue'
+import SelectControlled from '../examples/SelectControlled.vue'
+import Textarea from '../examples/Textarea.vue'
+import TextareaAutoresize from '../examples/TextareaAutoresize.vue'
+import TextareaControlled from '../examples/TextareaControlled.vue'
export default {
title: 'Components / Field',
@@ -13,3 +23,73 @@ export function basic() {
template: '',
}
}
+
+export function disabled() {
+ return {
+ components: { Disabled },
+ template: '',
+ }
+}
+
+export function input() {
+ return {
+ components: { Input },
+ template: '',
+ }
+}
+
+export function inputControlled() {
+ return {
+ components: { InputControlled },
+ template: '',
+ }
+}
+
+export function reactiveInvalid() {
+ return {
+ components: { ReactiveInvalid },
+ template: '',
+ }
+}
+
+export function requiredIndicator() {
+ return {
+ components: { RequiredIndicator },
+ template: '',
+ }
+}
+
+export function select() {
+ return {
+ components: { Select },
+ template: '',
+ }
+}
+
+export function selectControlled() {
+ return {
+ components: { SelectControlled },
+ template: '',
+ }
+}
+
+export function textarea() {
+ return {
+ components: { Textarea },
+ template: '',
+ }
+}
+
+export function textareaAutoresize() {
+ return {
+ components: { TextareaAutoresize },
+ template: '',
+ }
+}
+
+export function textareaControlled() {
+ return {
+ components: { TextareaControlled },
+ template: '',
+ }
+}
diff --git a/packages/vue/src/components/field/test/field.test.ts b/packages/vue/src/components/field/test/field.test.ts
index 5d1afa7..db1053c 100644
--- a/packages/vue/src/components/field/test/field.test.ts
+++ b/packages/vue/src/components/field/test/field.test.ts
@@ -1,15 +1,18 @@
-import { describe, expect, it } from 'vitest'
+import { describe, expect, it, vi } from 'vitest'
import { render } from 'vitest-browser-vue'
import { page, userEvent } from 'vitest/browser'
import { getExports, getParts } from '../../../../../../utils/test'
+import AllParts from '../examples/AllParts.vue'
import Basic from '../examples/Basic.vue'
import Disabled from '../examples/Disabled.vue'
import { Field, fieldAnatomy } from '../index'
describe('[field] component', () => {
it.each(getParts(fieldAnatomy))('should render part %s', async (part) => {
- render(Basic)
- expect(document.querySelector(part)).toBeInTheDocument()
+ render(AllParts)
+ await vi.waitFor(() => {
+ expect(document.querySelector(part)).not.toBeNull()
+ })
})
it.each(getExports(fieldAnatomy))('should export %s', async (part) => {