Components
Field
A form field wrapper with label, description, and error message, wrapping Morphos's Field/Fieldset.
Installation
npx kosmesis add fieldpnpm dlx kosmesis add fieldyarn dlx kosmesis add fieldbunx kosmesis add fieldInstall the following dependencies:
npm install @morphos/inputspnpm add @morphos/inputsyarn add @morphos/inputsbun add @morphos/inputsCopy and paste the following code into your project.
import { StatelessComponent } from "@praxisjs/core";import { Component } from "@praxisjs/decorators";import { Field as MorphosField, FieldControl as MorphosFieldControl, FieldDescription as MorphosFieldDescription, FieldError as MorphosFieldError, FieldLabel as MorphosFieldLabel, Fieldset as MorphosFieldset, type FieldControlProps as MorphosFieldControlProps, type FieldDescriptionProps as MorphosFieldDescriptionProps, type FieldErrorProps as MorphosFieldErrorProps, type FieldLabelProps as MorphosFieldLabelProps, type FieldsetProps as MorphosFieldsetProps} from "@morphos/inputs";import { cn } from "@/lib/utils";/** * Extends (not wraps) Morphos's `Field` so `new Field({ invalid: true })` still yields a real * instance with `.fieldId`/`.descriptionId`/`.errorId` — what `FieldLabel`/`FieldDescription`/ * `FieldError`/`FieldControl` need via their `field` prop. */@Component()export class Field extends MorphosField { render() { return ( <div class={cn("group/field flex flex-col gap-2", this.class)} data-invalid={this.invalid ? "" : undefined} data-disabled={this.disabled ? "" : undefined} data-required={this.required ? "" : undefined} > {this.children} </div> ); }}export type FieldLabelProps = MorphosFieldLabelProps;@Component()export class FieldLabel extends StatelessComponent<FieldLabelProps> { render() { const { class: cls, ...rest } = this.props; return ( <MorphosFieldLabel class={cn( "flex items-center gap-2 text-sm leading-none font-medium select-none group-data-disabled/field:pointer-events-none group-data-disabled/field:opacity-50", cls, )} {...rest} /> ); }}export type FieldDescriptionProps = MorphosFieldDescriptionProps;@Component()export class FieldDescription extends StatelessComponent<FieldDescriptionProps> { render() { const { class: cls, ...rest } = this.props; return <MorphosFieldDescription class={cn("text-sm text-muted-foreground", cls)} {...rest} />; }}export type FieldErrorProps = MorphosFieldErrorProps;@Component()export class FieldError extends StatelessComponent<FieldErrorProps> { render() { const { class: cls, ...rest } = this.props; return <MorphosFieldError class={cn("text-sm font-medium text-destructive", cls)} {...rest} />; }}export type FieldControlProps = MorphosFieldControlProps;@Component()export class FieldControl extends StatelessComponent<FieldControlProps> { render() { return <MorphosFieldControl {...this.props} />; }}/** Takes a `legend` string prop directly (rendered as a native `<legend>`) rather than a separate compound part. */export type FieldsetProps = MorphosFieldsetProps;@Component()export class Fieldset extends StatelessComponent<FieldsetProps> { render() { const { class: cls, ...rest } = this.props; return <MorphosFieldset class={cn("flex flex-col gap-6 [&>legend]:text-sm [&>legend]:font-medium", cls)} {...rest} />; }}Install the following dependencies:
npm install @morphos/inputs @praxisjs/csspnpm add @morphos/inputs @praxisjs/cssyarn add @morphos/inputs @praxisjs/cssbun add @morphos/inputs @praxisjs/cssCopy and paste the following code into your project.
import { StatelessComponent } from "@praxisjs/core";import { cx, Stylesheet, Styled, tokenVars } from "@praxisjs/css";import { Component } from "@praxisjs/decorators";import { Field as MorphosField, FieldControl as MorphosFieldControl, FieldDescription as MorphosFieldDescription, FieldError as MorphosFieldError, FieldLabel as MorphosFieldLabel, Fieldset as MorphosFieldset, type FieldControlProps as MorphosFieldControlProps, type FieldDescriptionProps as MorphosFieldDescriptionProps, type FieldErrorProps as MorphosFieldErrorProps, type FieldLabelProps as MorphosFieldLabelProps, type FieldsetProps as MorphosFieldsetProps} from "@morphos/inputs";import { KosmesisTokens } from "@/lib/kosmesis-theme";const t = tokenVars(KosmesisTokens);class FieldStyles extends Stylesheet { $root = this.css({ display: "flex", flexDirection: "column", gap: "0.5rem" }); /** Ancestor-prefixed nested rule — the `@praxisjs/css` equivalent of Tailwind's `group-data-disabled:`. */ $label = this.css({ display: "flex", alignItems: "center", gap: "0.5rem", fontSize: "0.875rem", lineHeight: 1, fontWeight: 500, userSelect: "none" }).on( "[data-disabled] &", { pointerEvents: "none", opacity: 0.5 }, ); $description = this.css({ fontSize: "0.875rem", color: t.mutedForeground }); $error = this.css({ fontSize: "0.875rem", fontWeight: 500, color: t.destructive }); $fieldset = this.css({ display: "flex", flexDirection: "column", gap: "1.5rem" }).on("& > legend", { fontSize: "0.875rem", fontWeight: 500, });}/** * Extends (not wraps) Morphos's `Field` so `new Field({ invalid: true })` still yields a real * instance with `.fieldId`/`.descriptionId`/`.errorId` — what `FieldLabel`/`FieldDescription`/ * `FieldError`/`FieldControl` need via their `field` prop. */@Component()export class Field extends MorphosField { @Styled(FieldStyles) $s!: FieldStyles; render() { return ( <div class={cx(this.$s.$root, this.class)} data-invalid={this.invalid ? "" : undefined} data-disabled={this.disabled ? "" : undefined} data-required={this.required ? "" : undefined} > {this.children} </div> ); }}export type FieldLabelProps = MorphosFieldLabelProps;@Component()export class FieldLabel extends StatelessComponent<FieldLabelProps> { @Styled(FieldStyles) $s!: FieldStyles; render() { const { class: cls, ...rest } = this.props; return <MorphosFieldLabel class={cx(this.$s.$label, cls)} {...rest} />; }}export type FieldDescriptionProps = MorphosFieldDescriptionProps;@Component()export class FieldDescription extends StatelessComponent<FieldDescriptionProps> { @Styled(FieldStyles) $s!: FieldStyles; render() { const { class: cls, ...rest } = this.props; return <MorphosFieldDescription class={cx(this.$s.$description, cls)} {...rest} />; }}export type FieldErrorProps = MorphosFieldErrorProps;@Component()export class FieldError extends StatelessComponent<FieldErrorProps> { @Styled(FieldStyles) $s!: FieldStyles; render() { const { class: cls, ...rest } = this.props; return <MorphosFieldError class={cx(this.$s.$error, cls)} {...rest} />; }}export type FieldControlProps = MorphosFieldControlProps;@Component()export class FieldControl extends StatelessComponent<FieldControlProps> { render() { return <MorphosFieldControl {...this.props} />; }}/** Takes a `legend` string prop directly (rendered as a native `<legend>`) rather than a separate compound part. */export type FieldsetProps = MorphosFieldsetProps;@Component()export class Fieldset extends StatelessComponent<FieldsetProps> { @Styled(FieldStyles) $s!: FieldStyles; render() { const { class: cls, ...rest } = this.props; return <MorphosFieldset class={cx(this.$s.$fieldset, cls)} {...rest} />; }}Examples
Usage
import { Field, FieldControl, FieldDescription, FieldError, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
@Component()
class SignupForm extends StatefulComponent {
@Prop() invalid = false;
@State() field = new Field({ invalid: this.invalid, required: true });
render() {
return (
<Field invalid={this.invalid} required>
<FieldLabel field={this.field}>Email</FieldLabel>
<FieldControl field={this.field}>
<Input id={this.field.fieldId} type="email" required invalid={this.invalid} />
</FieldControl>
<FieldDescription field={this.field}>We'll never share your email.</FieldDescription>
<FieldError field={this.field}>Please enter a valid email address.</FieldError>
</Field>
);
}
}FieldError only renders while field.invalid is true. Fieldset (also exported from this
file) wraps a native <fieldset> with a legend string prop, for grouping several Fields.
Props
Field
Extends Morphos's Field — instantiate it in state (@State() field = new Field({...})) and
pass it to every part below via their field prop.
| Prop | Type | Default |
|---|---|---|
invalid | boolean | false |
disabled | boolean | false |
required | boolean | false |
FieldLabel
| Prop | Type | Default |
|---|---|---|
field | Field | — required |
FieldDescription
| Prop | Type | Default |
|---|---|---|
field | Field | — required |
id | string | — |
FieldError
| Prop | Type | Default |
|---|---|---|
field | Field | — required |
id | string | — |
FieldControl
| Prop | Type | Default |
|---|---|---|
field | Field | — required |
Fieldset
| Prop | Type | Default |
|---|---|---|
disabled | boolean | — |
legend | string | — |
aria-label | string | — |
aria-labelledby | string | — |
aria-describedby | string | — |