Kosmesis
Components

Field

A form field wrapper with label, description, and error message, wrapping Morphos's Field/Fieldset.

Installation

npx kosmesis add field
pnpm dlx kosmesis add field
yarn dlx kosmesis add field
bunx kosmesis add field

Install the following dependencies:

npm install @morphos/inputs
pnpm add @morphos/inputs
yarn add @morphos/inputs
bun add @morphos/inputs

Copy and paste the following code into your project.

field.tsx
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/css
pnpm add @morphos/inputs @praxisjs/css
yarn add @morphos/inputs @praxisjs/css
bun add @morphos/inputs @praxisjs/css

Copy and paste the following code into your project.

field.tsx
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.

PropTypeDefault
invalidbooleanfalse
disabledbooleanfalse
requiredbooleanfalse

FieldLabel

PropTypeDefault
fieldField— required

FieldDescription

PropTypeDefault
fieldField— required
idstring

FieldError

PropTypeDefault
fieldField— required
idstring

FieldControl

PropTypeDefault
fieldField— required

Fieldset

PropTypeDefault
disabledboolean
legendstring
aria-labelstring
aria-labelledbystring
aria-describedbystring

On this page