Default

Loading interactive example. Full source is available below.

View example source
Download source
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import { useId } from "@coreai-ds/react-components";
import { Label } from "@coreai-ds/react-components";
import { Input } from "@coreai-ds/react-components";
import * as React from 'react';

export const Default = () => {
    const inputId = useId('input');
    const styles = /* css */ `
      .input-default-example {
        display: flex;
        flex-direction: column;
        gap: var(--spacingVerticalXXS);
        max-width: 400px;
      }
    `;
    return (<>
        <style>{styles}</style>
        <div className="input-default-example">
          <Label htmlFor={inputId}>Sample input</Label>
          <Input id={inputId}/>
        </div>
      </>);
};

export default Default;

Resources

Best practices

Do

  • Size the input to the approximate length of the expected content.
  • Use placeholder text only as a short supplementary hint or formatting example.
  • Associate placeholder text with an accessible label.

Don't

  • Use placeholder text for essential information needed to complete the input.
  • Rely on placeholder text instead of a persistent label or helper text.
  • Add a period to placeholder text.

Behavior and states

  • Inputs can be masked or use custom type attributes that affect their formatting.
  • Input masking can work like placeholder text to help people enter data in a specific format.
  • Inputs can appear standalone, as full-width block elements, or inline with text.
  • The input width should fit the approximate length of the expected content.
  • Put Input into Field if you need info buttons, helper text, and validation states.
API referenceGenerated CoreAI prop and type summaries

API

Generated CoreAI prop summaries, not replacement type declarations. Most native DOM/event and ARIA props are omitted. ? means optional; JSDoc shows defaults, deprecations, and conditional requirements. Defaults assume the standard CoreAI provider.

Input API

An Input component that extends Fluent UI's Input with an inline label.

interface InputProps {
  /**
   * Controls the colors and borders of the input.
   *
   * Note: 'filled-darker-shadow' and 'filled-lighter-shadow' are deprecated and will be removed in the future.
   * @default 'outline'
   */
  appearance?: "outline" | "filled-darker" | "filled-lighter" | "underline" | "filled-darker-shadow" | "filled-lighter-shadow";

  as?: "input";

  /**
   * Input can't have children.
   */
  children?: undefined;

  className?: string;

  /**
   * Element after the input text, within the input border
   */
  contentAfter?: WithSlotShorthandValue<{ as?: "span"; } & Omit<Omit<DetailedHTMLProps<HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "ref"> & { ref?: ((instance: HTMLSpanElement | null) => void) | RefObject<HTMLSpanElement> | null; }, "children"> & { children?: any; }> | null | undefined;

  /**
   * Element before the input text, within the input border
   */
  contentBefore?: WithSlotShorthandValue<{ as?: "span"; } & Omit<Omit<DetailedHTMLProps<HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "ref"> & { ref?: ((instance: HTMLSpanElement | null) => void) | RefObject<HTMLSpanElement> | null; }, "children"> & { children?: any; }> | null | undefined;

  /**
   * Default value of the input. Provide this if the input should be an uncontrolled component
   * which tracks its current state internally; otherwise, use `value`.
   *
   * (This prop is mutually exclusive with `value`.)
   */
  defaultValue?: string;

  /**
   * Collapse the control to its content width when nothing is selected and
   * grow it with the typed/selected content. Only available when `label` is set.
   */
  dynamicWidth?: boolean;

  /**
   * The actual `<input>` element. `type="text"` will be automatically applied unless overridden.
   *
   * This is the "primary" slot, so native props specified directly on the `<Input>` will go here
   * (except `className` and `style`, which go to the `root` slot). The top-level `ref` will
   * also go here.
   */
  input?: ({ as?: "input"; } & Omit<Omit<DetailedHTMLProps<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>, "ref"> & { ref?: ((instance: HTMLInputElement | null) => void) | RefObject<HTMLInputElement> | null; }, "children"> & { children?: undefined; }) | undefined;

  /**
   * Optional inline label rendered inside the control, before the value.
   */
  label?: string;

  /**
   * Called when the user changes the input's value.
   */
  onChange?: ((ev: ChangeEvent<HTMLInputElement>, data: InputOnChangeData) => void);

  ref?: Ref<HTMLInputElement>;

  /**
   * Wrapper element which visually appears to be the input and is used for borders, focus styling, etc.
   * (A wrapper is needed to properly position `contentBefore` and `contentAfter` relative to `input`.)
   *
   * The root slot receives the `className` and `style` specified directly on the `<Input>`.
   * All other top-level native props will be applied to the primary slot, `input`.
   */
  root?: NonNullable<WithSlotShorthandValue<{ as?: "span"; } & Omit<Omit<DetailedHTMLProps<HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "ref"> & { ref?: ((instance: HTMLSpanElement | null) => void) | RefObject<HTMLSpanElement> | null; }, "children"> & { children?: any; }> | null> | undefined;

  /**
   * Size of the input (changes the font size and spacing).
   * @default 'medium'
   */
  size?: "small" | "medium" | "large";

  style?: CSSProperties;

  /**
   * Controls text overflow behavior for the value. When true, long text is truncated with an ellipsis.
   */
  truncateTextOverflow?: boolean;

  /**
   * An input can have different text-based [types](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Input#input_types)
   * based on the type of value the user will enter.
   *
   * Note that no custom styling is currently applied for alternative types, and some types may
   * activate browser-default styling which does not match the Fluent design language.
   *
   * (For non-text-based types such as `button` or `checkbox`, use the appropriate component or an
   * `<input>` element instead.)
   * @default 'text'
   */
  type?: "number" | "search" | "time" | "text" | "tel" | "url" | "email" | "date" | "datetime-local" | "month" | "password" | "week";

  /**
   * Current value of the input. Provide this if the input is a controlled component where you
   * are maintaining its current state; otherwise, use `defaultValue`.
   *
   * (This prop is mutually exclusive with `defaultValue`.)
   */
  value?: string;
}

Implementation

CoreAI 0.12.0; Fluent 9.74.9.

Setup and dependencies.

Import components from @coreai-ds/react-components. CoreAI guidance above takes precedence over API demonstration examples.

Appearance

Loading interactive example. Full source is available below.

Download source
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import { useId } from "@coreai-ds/react-components";
import { Label } from "@coreai-ds/react-components";
import { Input } from "@coreai-ds/react-components";
import * as React from 'react';

// ---------- Appearance ----------
export const Appearance = () => {
    const idPrefix = 'input-appearance-story';
    const inputIds = {
        filledDarker: useId(idPrefix),
        outline: useId(idPrefix),
        underline: useId(idPrefix),
        filledLighter: useId(idPrefix),
    };
    const styles = /* css */ `
    .input-appearance-example {
      display: flex;
      flex-direction: column;
      max-width: 400px;
    }

    .input-appearance-example__field {
      display: grid;
      row-gap: var(--spacingVerticalXXS);
      margin-top: var(--spacingVerticalL);
    }

    .input-appearance-example__field--filled-lighter {
      margin-inline: -48px;
      padding-block: var(--spacingVerticalXXL);
      padding-inline: 48px;
      background-color: var(--colorNeutralBackground3);
    }
  `;
    return (<>
      <style>{styles}</style>
      <div className="input-appearance-example">
        <div className="input-appearance-example__field">
          <Label htmlFor={inputIds.outline}>Outline appearance</Label>
          <Input appearance="outline" id={inputIds.outline}/>
        </div>
        <div className="input-appearance-example__field">
          <Label htmlFor={inputIds.filledDarker}>Filled darker appearance (CoreAI default)</Label>
          <Input appearance="filled-darker" id={inputIds.filledDarker}/>
        </div>
        <div className="input-appearance-example__field input-appearance-example__field--filled-lighter">
          <Label htmlFor={inputIds.filledLighter}>Filled lighter appearance</Label>
          <Input appearance="filled-lighter" id={inputIds.filledLighter}/>
        </div>
        <div className="input-appearance-example__field">
          <Label htmlFor={inputIds.underline}>Underline appearance</Label>
          <Input appearance="underline" id={inputIds.underline}/>
        </div>
      </div>
    </>);
};

export default Appearance;

Content before/after

Loading interactive example. Full source is available below.

Download source
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import type { JSXElement } from "@coreai-ds/react-components";
import { makeStyles } from "@coreai-ds/react-components";
import { useId } from "@coreai-ds/react-components";
import { Label } from "@coreai-ds/react-components";
import { Input } from "@coreai-ds/react-components";
import { PersonRegular } from "@fluentui/react-icons";
import { Body1 } from "@coreai-ds/react-components";
import * as React from 'react';
import type { ButtonProps } from "@coreai-ds/react-components";
import { Button } from "@coreai-ds/react-components";
import { MicRegular } from "@fluentui/react-icons";
import { Text } from "@coreai-ds/react-components";

const useStyles = makeStyles({
    root: {
        display: 'flex',
        flexDirection: 'column',
        gap: '20px',
        // Prevent the example from taking the full width of the page (optional)
        maxWidth: '400px',
        // Stack the label above the field (with 2px gap per the design system)
        '> div': { display: 'flex', flexDirection: 'column', gap: '2px' },
    },
});

const MicButton: React.FC<ButtonProps> = props => {
    return <Button {...props} appearance="transparent" icon={<MicRegular />} size="small"/>;
};

export const ContentBeforeAfter = (): JSXElement => {
    const styles = useStyles();
    const beforeId = useId('content-before');
    const afterId = useId('content-after');
    const beforeAndAfterId = useId('content-before-and-after');
    const beforeLabelId = useId('before-label');
    const afterLabelId = useId('after-label');
    return (<div className={styles.root}>
      <div>
        <Label htmlFor={beforeId}>Full name</Label>
        <Input contentBefore={<PersonRegular />} id={beforeId}/>
        <Body1>
          An input with a decorative icon in the <code>contentBefore</code> slot.
        </Body1>
      </div>

      <div>
        <Label htmlFor={afterId}>First name</Label>
        <Input contentAfter={<MicButton aria-label="Enter by voice"/>} id={afterId}/>
        <Body1>
          An input with a button in the <code>contentAfter</code> slot.
        </Body1>
      </div>

      <div>
        <Label htmlFor={beforeAndAfterId}>Amount to pay</Label>
        <Input contentBefore={<Text size={400} id={beforeLabelId}>
              $
            </Text>} contentAfter={<Text size={400} id={afterLabelId}>
              .00
            </Text>} aria-labelledby={`${beforeAndAfterId} ${beforeLabelId} ${afterLabelId}`} id={beforeAndAfterId}/>
        <Body1>
          An input with a presentational value in the <code>contentBefore</code> slot and another presentational value
          in the <code>contentAfter</code> slot.
        </Body1>
      </div>
    </div>);
};

export default ContentBeforeAfter;

Controlled

Loading interactive example. Full source is available below.

Download source
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import type { JSXElement } from "@coreai-ds/react-components";
import { useId } from "@coreai-ds/react-components";
import * as React from 'react';
import { makeStyles } from "@coreai-ds/react-components";
import type { InputProps } from "@coreai-ds/react-components";
import { Label } from "@coreai-ds/react-components";
import { Input } from "@coreai-ds/react-components";

const useStyles = makeStyles({
    root: {
        display: 'flex',
        flexDirection: 'column',
        // Use 2px gap below the label (per the design system)
        gap: '2px',
        // Prevent the example from taking the full width of the page (optional)
        maxWidth: '400px',
    },
});

export const Controlled = (): JSXElement => {
    const inputId = useId('input');
    const [value, setValue] = React.useState('initial value');
    const styles = useStyles();
    const onChange: InputProps['onChange'] = (ev, data) => {
        // The controlled input pattern can be used for other purposes besides validation,
        // but validation is a useful example
        if (data.value.length <= 20) {
            setValue(data.value);
        }
    };
    return (<div className={styles.root}>
      <Label htmlFor={inputId}>Controlled input limiting the value to 20 characters</Label>
      <Input value={value} onChange={onChange} id={inputId}/>
    </div>);
};

export default Controlled;

Disabled

Loading interactive example. Full source is available below.

Download source
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import { useId } from "@coreai-ds/react-components";
import * as React from 'react';
import { Label } from "@coreai-ds/react-components";
import { Input } from "@coreai-ds/react-components";
import { Switch } from "@coreai-ds/react-components";

// ---------- Disabled ----------
export const Disabled = () => {
    const idPrefix = 'input-disabled-story';
    const inputIds = {
        default: useId(idPrefix),
        filledDarker: useId(idPrefix),
        filledLighter: useId(idPrefix),
        underline: useId(idPrefix),
    };
    const [disabled, setDisabled] = React.useState(true);
    const styles = /* css */ `
    .input-disabled-example {
      display: flex;
      flex-direction: column;
      max-width: 400px;
    }

    .input-disabled-example__field {
      display: grid;
      row-gap: var(--spacingVerticalXXS);
      margin-top: var(--spacingVerticalL);
    }

    .input-disabled-example__field--filled-lighter {
      margin-inline: -48px;
      padding-block: var(--spacingVerticalXXL);
      padding-inline: 48px;
      background-color: var(--colorNeutralBackground3);
    }

    .input-disabled-example__toggle {
      margin-top: var(--spacingVerticalXL);
    }
  `;
    return (<>
      <style>{styles}</style>
      <div className="input-disabled-example">
        <div className="input-disabled-example__field">
          <Label htmlFor={inputIds.default}>Disabled (outline appearance)</Label>
          <Input appearance="outline" disabled={disabled} id={inputIds.default} defaultValue="disabled value"/>
        </div>
        <div className="input-disabled-example__field">
          <Label htmlFor={inputIds.filledDarker}>
            Disabled (filled darker appearance, CoreAI default)
          </Label>
          <Input appearance="filled-darker" disabled={disabled} id={inputIds.filledDarker} defaultValue="disabled value"/>
        </div>
        <div className="input-disabled-example__field input-disabled-example__field--filled-lighter">
          <Label htmlFor={inputIds.filledLighter}>Disabled (filled lighter appearance)</Label>
          <Input appearance="filled-lighter" disabled={disabled} id={inputIds.filledLighter} defaultValue="disabled value"/>
        </div>
        <div className="input-disabled-example__field">
          <Label htmlFor={inputIds.underline}>Disabled (underline appearance)</Label>
          <Input appearance="underline" disabled={disabled} id={inputIds.underline} defaultValue="disabled value"/>
        </div>
        <Switch className="input-disabled-example__toggle" checked={disabled} label="Disabled" onChange={(_e, data) => setDisabled(data.checked)}/>
      </div>
    </>);
};

export default Disabled;

Inline

Loading interactive example. Full source is available below.

Download source
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import type { JSXElement } from "@coreai-ds/react-components";
import { useId } from "@coreai-ds/react-components";
import { Label } from "@coreai-ds/react-components";
import { Input } from "@coreai-ds/react-components";
import * as React from 'react';

export const Inline = (): JSXElement => {
    const inputId = useId('input');
    return (<div>
      <Label htmlFor={inputId} style={{ paddingInlineEnd: '12px' }}>
        Sample inline input
      </Label>
      <Input id={inputId}/>

      <p>
        This input is <Input placeholder="inline" aria-label="inline"/> within a paragraph of text (be sure to provide
        an <code>aria-label</code>).
      </p>
    </div>);
};

export default Inline;

Inline Label

Loading interactive example. Full source is available below.

Download source
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import * as React from 'react';
import { Input } from "@coreai-ds/react-components";

export const InlineLabel = () => {
    const [name, setName] = React.useState<string>('');
    const styles = /* css */ `
      .input-inline-label-example {
        display: flex;
        flex-direction: column;
        align-items: flex-start;
        row-gap: var(--spacingVerticalL);
        max-width: 320px;
        padding: var(--spacingVerticalL);
      }

      .input-inline-label-example__field {
        display: flex;
        flex-direction: column;
        align-items: flex-start;
        row-gap: var(--spacingVerticalXS);
      }

      .input-inline-label-example__prop-label {
        font-family: var(--fontFamilyMonospace);
        font-size: var(--fontSizeBase200);
        color: var(--colorNeutralForeground3);
      }
    `;
    return (<>
        <style>{styles}</style>
        <div className="input-inline-label-example">
          <div className="input-inline-label-example__field">
            <code className="input-inline-label-example__prop-label">label</code>
            <Input label="Name" placeholder="Enter a name" value={name} onChange={(_event, data) => setName(data.value)}/>
          </div>

          <div className="input-inline-label-example__field">
            <code className="input-inline-label-example__prop-label">label, dynamicWidth</code>
            <Input label="Name" dynamicWidth placeholder="Enter a name"/>
          </div>
        </div>
      </>);
};

export default InlineLabel;

Placeholder

Loading interactive example. Full source is available below.

Download source
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import type { JSXElement } from "@coreai-ds/react-components";
import { useId } from "@coreai-ds/react-components";
import { makeStyles } from "@coreai-ds/react-components";
import { Label } from "@coreai-ds/react-components";
import { Input } from "@coreai-ds/react-components";
import * as React from 'react';

const useStyles = makeStyles({
    root: {
        display: 'flex',
        flexDirection: 'column',
        rowGap: '5px',
        maxWidth: '300px',
    },
});

export const Placeholder = (): JSXElement => {
    const inputId = useId('input-with-placeholder');
    const styles = useStyles();
    return (<div className={styles.root}>
      <Label htmlFor={inputId}>Input with a placeholder</Label>
      <Input placeholder="This is a placeholder" id={inputId}/>
    </div>);
};

export default Placeholder;

Read Only

Loading interactive example. Full source is available below.

Download source
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import { useId } from "@coreai-ds/react-components";
import { Label } from "@coreai-ds/react-components";
import { Input } from "@coreai-ds/react-components";
import * as React from 'react';

export const ReadOnly = () => {
    const idPrefix = 'input-readonly-story';
    const inputIds = {
        token: useId(idPrefix),
        editable: useId(idPrefix),
    };
    const styles = /* css */ `
      .input-read-only-example {
        display: flex;
        flex-direction: column;
        max-width: 400px;
      }

      .input-read-only-example__field {
        display: grid;
        row-gap: var(--spacingVerticalXXS);
        margin-top: var(--spacingVerticalL);
      }
    `;
    return (<>
        <style>{styles}</style>
        <div className="input-read-only-example">
          <div className="input-read-only-example__field">
            <Label htmlFor={inputIds.token}>Read-only (with copy button)</Label>
            <Input readOnly id={inputIds.token} value="sk-9f3c1a7b2e4d6f80"/>
          </div>

          <div className="input-read-only-example__field">
            <Input readOnly label="API key" value="sk-9f3c1a7b2e4d6f80"/>
          </div>

          <div className="input-read-only-example__field">
            <Label htmlFor={inputIds.editable}>Editable (no copy button)</Label>
            <Input id={inputIds.editable} defaultValue="Type to edit me"/>
          </div>
        </div>
      </>);
};

export default ReadOnly;

Size

Loading interactive example. Full source is available below.

Download source
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import type { JSXElement } from "@coreai-ds/react-components";
import { useId } from "@coreai-ds/react-components";
import { makeStyles } from "@coreai-ds/react-components";
import { Label } from "@coreai-ds/react-components";
import { Input } from "@coreai-ds/react-components";
import * as React from 'react';

const useStyles = makeStyles({
    root: {
        display: 'flex',
        flexDirection: 'column',
        gap: '20px',
        maxWidth: '400px',
        // Stack the label above the field (with 2px gap per the design system)
        '> div': { display: 'flex', flexDirection: 'column', gap: '2px' },
    },
});

export const Size = (): JSXElement => {
    const smallId = useId('input-small');
    const mediumId = useId('input-medium');
    const largeId = useId('input-large');
    const styles = useStyles();
    return (<div className={styles.root}>
      <div>
        <Label size="small" htmlFor={smallId}>
          Small input
        </Label>
        <Input size="small" id={smallId}/>
      </div>

      <div>
        <Label size="medium" htmlFor={mediumId}>
          Medium input
        </Label>
        <Input size="medium" id={mediumId}/>
      </div>

      <div>
        <Label size="large" htmlFor={largeId}>
          Large input
        </Label>
        <Input size="large" id={largeId}/>
      </div>
    </div>);
};

export default Size;

Type

Loading interactive example. Full source is available below.

Download source
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import type { JSXElement } from "@coreai-ds/react-components";
import { useId } from "@coreai-ds/react-components";
import { makeStyles } from "@coreai-ds/react-components";
import { Label } from "@coreai-ds/react-components";
import { Input } from "@coreai-ds/react-components";
import * as React from 'react';

const useStyles = makeStyles({
    root: {
        display: 'flex',
        flexDirection: 'column',
        gap: '20px',
        maxWidth: '400px',
        // Stack the label above the field (with 2px gap per the design system)
        '> div': { display: 'flex', flexDirection: 'column', gap: '2px' },
    },
});

export const Type = (): JSXElement => {
    const emailId = useId('input-email');
    const urlId = useId('input-url');
    const passwordId = useId('input-password');
    const styles = useStyles();
    return (<form noValidate autoComplete="off" className={styles.root}>
      <div>
        <Label htmlFor={emailId}>Email input</Label>
        <Input type="email" id={emailId}/>
      </div>

      <div>
        <Label htmlFor={urlId}>URL input</Label>
        <Input type="url" id={urlId}/>
      </div>

      <div>
        <Label htmlFor={passwordId}>Password input</Label>
        <Input type="password" defaultValue="password" id={passwordId}/>
      </div>
    </form>);
};

export default Type;

Uncontrolled

Loading interactive example. Full source is available below.

Download source
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import type { JSXElement } from "@coreai-ds/react-components";
import { useId } from "@coreai-ds/react-components";
import { makeStyles } from "@coreai-ds/react-components";
import { Label } from "@coreai-ds/react-components";
import { Input } from "@coreai-ds/react-components";
import type { InputProps } from "@coreai-ds/react-components";
import * as React from 'react';

const useStyles = makeStyles({
    root: {
        // Stack the label above the field
        display: 'flex',
        flexDirection: 'column',
        // Use 2px gap below the label (per the design system)
        gap: '2px',
        // Prevent the example from taking the full width of the page (optional)
        maxWidth: '400px',
    },
});

const onChange: InputProps['onChange'] = (ev, data) => {
    // Uncontrolled inputs can be notified of changes to the value
    console.log(`New value: "${data.value}"`);
};

export const Uncontrolled = (): JSXElement => {
    const inputId = useId('input');
    const styles = useStyles();
    return (<div className={styles.root}>
      <Label htmlFor={inputId}>Uncontrolled input with default value</Label>
      <Input defaultValue="default value" onChange={onChange} id={inputId}/>
    </div>);
};

export default Uncontrolled;