COMPONENTS/INPUTS

Field.

Label, control and feedback in one place.

In practice

This appears on your workspace.

Coal connects labels and descriptions. Set invalid to show an error. Use FieldControl for automatic association.

Installation

Install Coal once. Components and TypeScript declarations are included. React 19 and React DOM 19 are peer dependencies.

Terminal · local package
npm install http://localhost:3100/downloads/chlohal-coal-ui-0.5.0.tgz

This installs the local package. It is not yet published on npm. See installation for stylesheet setup and integration.

Usage

example.tsx
"use client";
import * as React from "react";
import "@chlohal/coal-ui/styles.css";
import { Input, Field, FieldLabel, FieldControl, FieldDescription } from "@chlohal/coal-ui";
export default function Example() {
    return (<Field>
          <FieldLabel>Studio name</FieldLabel>
          <FieldControl render={<Input />} placeholder="Your independent studio"/>
          <FieldDescription>This appears on your workspace.</FieldDescription>
        </Field>);
}

Source

The original Coal implementation. Use the package export to integrate it.

packages/react/src/field.tsx
"use client";
import * as React from "react";
import { cn, useRequired } from "./internal.js";
const Context = React.createContext<
  { id: string; invalid?: boolean } | undefined
>(undefined);
export function Field({
  invalid,
  children,
  className,
  id,
  ...props
}: React.ComponentPropsWithRef<"div"> & { invalid?: boolean }) {
  const generated = React.useId();
  return (
    <Context.Provider value={{ id: id ?? generated, invalid }}>
      <div className={cn("coal-field", className)} {...props}>
        {children}
      </div>
    </Context.Provider>
  );
}
export function FieldLabel({
  className,
  ...props
}: React.ComponentPropsWithRef<"label">) {
  const c = useRequired(Context, "FieldLabel");
  return (
    <label htmlFor={c.id} className={cn("coal-label", className)} {...props} />
  );
}
export function FieldControl({
  render,
  ...props
}: React.ComponentPropsWithRef<"input"> & { render?: React.ReactElement }) {
  const c = useRequired(Context, "FieldControl");
  const merged = {
    id: c.id,
    "aria-invalid": c.invalid,
    "aria-describedby": `${c.id}-description${c.invalid ? ` ${c.id}-error` : ""}`,
    ...props,
  };
  return render ? (
    React.cloneElement(
      render as React.ReactElement<Record<string, unknown>>,
      merged,
    )
  ) : (
    <input className="coal-input" {...merged} />
  );
}
export function FieldDescription({
  className,
  ...props
}: React.ComponentPropsWithRef<"p">) {
  const c = useRequired(Context, "FieldDescription");
  return (
    <p
      {...props}
      id={`${c.id}-description`}
      className={cn("coal-field-description", className)}
    />
  );
}
export function FieldError({
  className,
  ...props
}: React.ComponentPropsWithRef<"p">) {
  const c = useRequired(Context, "FieldError");
  return c.invalid ? (
    <p
      {...props}
      id={`${c.id}-error`}
      role="alert"
      className={cn("coal-field-error", className)}
    />
  ) : null;
}

Accessibility checklist

  • Provide an accessible name for controls, including icon-only buttons.
  • Check keyboard navigation, visible focus and disabled states in your application.
  • Do not rely on color alone to communicate status.