Formfield React.js component with Tailwind CSS

January 05, 2022

2 min read

views

Component

The component will add the required accessibility attributes to the input, label and error message elements thanks to react-aria.

components/Formfield.tsx
import * as React from "react";
import { useField } from "@react-aria/label";
import classnames from "classnames";

interface Props {
  /** the label that represents the field */
  label: string;
  children: React.ReactNode;

  /** add extra classes to the form field */
  className?: string;

  /** an error message for the field */
  errorMessage?: string;

  /** make a form field as optional */
  optional?: boolean;
}

export function Formfield({ children, label, className, errorMessage, optional }: Props) {
  const { labelProps, fieldProps, errorMessageProps } = useField({ label, errorMessage });

  const labelClassnames = classnames("mb-1 dark:text-white");
  // first child must be an input, textarea, select, etc. (form elements)
  const [child, ...rest] = Array.isArray(children) ? children : [children];

  const element = React.cloneElement(child as React.ReactElement<any>, {
    ...fieldProps,
  });

  return (
    <div className={classnames("flex mb-3", className)}>
      <label {...labelProps} className={labelClassnames}>
        {label} {optional ? <span className="text-sm italic">(Optional)</span> : null}
      </label>

      {element}
      {rest}

      {errorMessage ? (
        <span {...errorMessageProps} className="mt-1 font-medium text-red-500">
          {errorMessage}
        </span>
      ) : null}
    </div>
  );
}

Usage

// can come from a form library such as Formik or react-hook-form
const errors = {};
const values = {};

export function MyForm() {
  return (
    <form>
      <Formfield label="Enter name" errorMessage={errors.myName}>
        <MyInput name="myName" value={values.myName} />
      </Formfield>

      <Formfield label="Enter age" optional errorMessage={errors.age}>
        <MyInput type="number" name="age" value={values.age} />
      </Formfield>
    </form>
  );
}