Kosmesis
Components

Label

An accessible label, typically paired with Morphos's Field. Purely presentational.

Installation

npx kosmesis add label
pnpm dlx kosmesis add label
yarn dlx kosmesis add label
bunx kosmesis add label

Copy and paste the following code into your project.

label.tsx
import { StatelessComponent } from "@praxisjs/core";import { Component } from "@praxisjs/decorators";import type { Children } from "@praxisjs/shared";import { cn } from "@/lib/utils";export interface LabelProps {  htmlFor?: string;  class?: string;  id?: string;  children?: Children;}/** * Purely presentational — no Morphos equivalent, same as upstream shadcn/ui. Pairs naturally * with Morphos's `Field` (pass `field.fieldId` as `htmlFor`) but isn't coupled to it, so it also * works as a plain standalone label. */@Component()export class Label extends StatelessComponent<LabelProps> {  render() {    const { htmlFor, class: cls, id, children } = this.props;    return (      <label        id={id}        htmlFor={htmlFor}        class={cn(          "flex select-none items-center gap-2 text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-50 group-data-disabled:pointer-events-none group-data-disabled:opacity-50",          cls,        )}      >        {children}      </label>    );  }}

Install the following dependencies:

npm install @praxisjs/css
pnpm add @praxisjs/css
yarn add @praxisjs/css
bun add @praxisjs/css

Copy and paste the following code into your project.

label.tsx
import { StatelessComponent } from "@praxisjs/core";import { cx, Stylesheet, Styled } from "@praxisjs/css";import { Component } from "@praxisjs/decorators";import type { Children } from "@praxisjs/shared";class LabelStyles extends Stylesheet {  $root = this.css({    display: "flex",    userSelect: "none",    alignItems: "center",    gap: "0.5rem",    fontSize: "0.875rem",    fontWeight: 500,    lineHeight: 1,  })    // Ancestor-prefixed nested rule: applies when a preceding sibling with the peer marker is    // `:disabled`, or an ancestor carries `[data-disabled]` — the `@praxisjs/css` equivalent of    // Tailwind's `peer-disabled:`/`group-data-disabled:` variants, since CSS nesting allows `&`    // anywhere in a compound selector, not just as a prefix.    .on("[data-kosmesis-peer]:disabled ~ &", { cursor: "not-allowed", opacity: 0.5 })    .on("[data-disabled] &", { pointerEvents: "none", opacity: 0.5 });}export interface LabelProps {  htmlFor?: string;  class?: string;  id?: string;  children?: Children;}/** * Purely presentational — no Morphos equivalent, same as upstream shadcn/ui. Pairs naturally * with Morphos's `Field` (pass `field.fieldId` as `htmlFor`) but isn't coupled to it, so it also * works as a plain standalone label. */@Component()export class Label extends StatelessComponent<LabelProps> {  @Styled(LabelStyles) $s!: LabelStyles;  render() {    const { htmlFor, class: cls, id, children } = this.props;    return (      <label id={id} htmlFor={htmlFor} class={cx(this.$s.$root, cls)}>        {children}      </label>    );  }}

Examples

Usage

import { Label } from "@/components/ui/label";
import { Input } from "@/components/ui/input";

<div class="flex flex-col gap-2">
  <Label htmlFor="email">Email</Label>
  <Input id="email" type="email" placeholder="[email protected]" />
</div>

Pairing with Field

import { Field, FieldControl, FieldDescription, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";

@Component()
class MyForm extends StatefulComponent {
  // A second, separately-tracked instance — the same "instantiate once, pass to every part"
  // pattern every Morphos compound component uses. The JSX-mounted <Field> below produces the
  // actual container element; `this.field` is what FieldLabel/Control/Description read from.
  @State() field = new Field();

  render() {
    return (
      <Field>
        <FieldLabel field={this.field}>Email</FieldLabel>
        <FieldControl field={this.field}>
          <Input id={this.field.fieldId} />
        </FieldControl>
        <FieldDescription field={this.field}>We'll never share your email.</FieldDescription>
      </Field>
    );
  }
}

Label itself doesn't require a Field — pass htmlFor pointing at any input's id for a plain standalone label.

Props

PropTypeDefault
htmlForstring

On this page