Monet
@etched/monet 0.0.1

Input group

Ported

An input with icons, text or a button inside its frame, for example a search field. With label, it is wrapped in a Field.

Kumo source
import { MagnifyingGlassIcon } from "@phosphor-icons/react";
import { InputGroup, InputGroupAddon, InputGroupInput } from "@etched/monet/input-group";

export function Preview() {
  return (
    <InputGroup>
      <InputGroupAddon>
        <MagnifyingGlassIcon />
      </InputGroupAddon>
      <InputGroupInput type="search" aria-label="Search candidates" placeholder="Search candidates" />
    </InputGroup>
  );
}

Installation

import { InputGroup, InputGroupAddon, InputGroupInput, InputGroupSuffix, InputGroupTextarea } from "@etched/monet/input-group";

Examples

Text and sizes

https://
⌘K
import { MagnifyingGlassIcon } from "@phosphor-icons/react";
import { InputGroup, InputGroupAddon, InputGroupInput } from "@etched/monet/input-group";

export function TextAndSizes() {
  return (
    <div className="grid">
      <InputGroup>
        <InputGroupAddon>https://</InputGroupAddon>
        <InputGroupInput aria-label="Site" defaultValue="tracer.etched.com" />
      </InputGroup>
      <InputGroup size="sm">
        <InputGroupAddon>
          <MagnifyingGlassIcon />
        </InputGroupAddon>
        <InputGroupInput aria-label="Filter rows" placeholder="Filter rows" />
        <InputGroupAddon align="end">⌘K</InputGroupAddon>
      </InputGroup>
    </div>
  );
}

Suffix

InputGroupSuffix follows the typed value, so the user sees the whole address as they type.

.etched.ai

Letters, numbers and dashes.

import { InputGroup, InputGroupInput, InputGroupSuffix } from "@etched/monet/input-group";

export function Suffix() {
  return (
    <div className="grid">
      <InputGroup label="Workspace address" description="Letters, numbers and dashes.">
        <InputGroupInput defaultValue="tracer" />
        <InputGroupSuffix>.etched.ai</InputGroupSuffix>
      </InputGroup>
    </div>
  );
}

Joined button

A Button placed directly in the group, not in an addon, joins the frame's end with its own edge and focus.

import { MagnifyingGlassIcon } from "@phosphor-icons/react";
import { Button } from "@etched/monet/button";
import { InputGroup, InputGroupAddon, InputGroupInput } from "@etched/monet/input-group";

export function JoinedButton() {
  return (
    <div className="grid">
      <InputGroup>
        <InputGroupAddon>
          <MagnifyingGlassIcon />
        </InputGroupAddon>
        <InputGroupInput type="search" aria-label="Search candidates" placeholder="Name, school or company" />
        <Button>Search</Button>
      </InputGroup>
      <InputGroup size="sm">
        <InputGroupInput type="email" aria-label="Invite by email" placeholder="name@etched.ai" />
        <Button size="sm" variant="primary">
          Invite
        </Button>
      </InputGroup>
    </div>
  );
}

Comment box

InputGroupTextarea grows with its text. Addons with align="top" or "bottom" go above or below it; a button that comes last goes to the end.

Visible to the hiring team
import { useState } from "react";
import { Button } from "@etched/monet/button";
import { InputGroup, InputGroupAddon, InputGroupTextarea } from "@etched/monet/input-group";

export function CommentBox() {
  const [comment, setComment] = useState("");

  return (
    <div className="grid">
      <InputGroup label="Comment">
        <InputGroupTextarea placeholder="Add a comment for the hiring team" maxRows={8} value={comment} onValueChange={setComment} />
        <InputGroupAddon align="bottom">
          <span>Visible to the hiring team</span>
          <Button size="sm" variant="primary" disabled={!comment.trim()} onClick={() => setComment("")}>
            Send
          </Button>
        </InputGroupAddon>
      </InputGroup>
    </div>
  );
}

In a field

label, description and error wrap the group in a Field; the label names the field inside. required={false} adds the optional marker.

L-

Lots start with L-.

@etched.com
Enter an email address.
https://

Read-only: set by the scheduling tool.

import { LinkIcon } from "@phosphor-icons/react";
import { InputGroup, InputGroupAddon, InputGroupInput } from "@etched/monet/input-group";

export function InAField() {
  return (
    <div className="grid">
      <InputGroup label="Lot" description="Lots start with L-.">
        <InputGroupAddon>L-</InputGroupAddon>
        <InputGroupInput placeholder="7801" />
      </InputGroup>
      <InputGroup label="Owner email" error="Enter an email address.">
        <InputGroupInput type="email" defaultValue="sam@" />
        <InputGroupAddon align="end">@etched.com</InputGroupAddon>
      </InputGroup>
      <InputGroup label="Portfolio" required={false}>
        <InputGroupAddon>https://</InputGroupAddon>
        <InputGroupInput placeholder="example.com" />
      </InputGroup>
      <InputGroup label="Calendar link" description="Read-only: set by the scheduling tool.">
        <InputGroupAddon>
          <LinkIcon />
        </InputGroupAddon>
        <InputGroupInput defaultValue="cal.etched.ai/ana-ruiz" readOnly />
      </InputGroup>
    </div>
  );
}

Accessibility

  • The group is a plain div with no role. The field inside is named by the group's label, or by its own aria-label.
  • Text and the suffix in addons are not linked to the field, so screen readers do not hear them with it. Icons are hidden.
  • Put needed addon text, such as a unit or the suffix, in the label or description too. Give an icon-only button aria-label.
  • Put required on the field inside. The group's required={false} only adds the optional marker.

API reference

InputGroupAddon

PropTypeDefaultDescription
align"start" | "end" | "top" | "bottom""start"Before or after the field, whatever its place in the code; above or below a textarea.
children *ReactNode–An icon, short text, or a small ghost Button (an icon-only one needs aria-label). Above or below a textarea, a button that comes last goes to the end.
classNamestring–

InputGroup

PropTypeDefaultDescription
labelReactNode–Visible label. Without one, give the control an aria-label.
labelTooltipReactNode–More about the field, behind an info button after the label.
labelTooltipLabelstring"More information"Names the info button.
descriptionReactNode–Help text below the control. Hidden while an error shows.
errorFieldError–
size"sm" | "base""base"
requiredboolean–false adds the optional marker after the label. To make the value required, put the native required on the field inside.
children *ReactNode–An InputGroupInput or InputGroupTextarea, with InputGroupAddons around it and an InputGroupSuffix after it. A Button placed directly in the group (not in an addon) is joined to the frame's end, with its own edge.
classNamestring–

InputGroupSuffix

PropTypeDefaultDescription
children *ReactNode–Text that follows the typed value, such as ".etched.ai".
classNamestring–

InputGroupTextarea

PropTypeDefaultDescription
onValueChange(value: string) => void–Called with the new text on each change.
minRowsnumber2The fewest lines it shows.
maxRowsnumber–The most lines it grows to; past them the text scrolls.