> ## Documentation Index
> Fetch the complete documentation index at: https://anvil.servicetitan.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Date Field Yearless – Design

> A form field for selecting dates without year information, useful for recurring dates.

export const DoDont = ({text, type, children}) => {
  return <>
      {type === "do" && <div className="do-dont do">
          {children && <div className="do-dont-content">{children}</div>}
          <Check>
            <p>
              <strong>Do</strong>
              {text && <span className="m-inline-start-1">{text}</span>}
            </p>
          </Check>
        </div>}
      {type === "dont" && <div className="do-dont dont">
          {children && <div className="do-dont-content">{children}</div>}
          <Danger>
            <p>
              <strong>Don't</strong>
              {text && <span className="m-inline-start-1">{text}</span>}
            </p>
          </Danger>
        </div>}
      {type === "caution" && <div className="do-dont caution">
          {children && <div className="do-dont-content">{children}</div>}
          <Warning>
            <p>
              <strong>Caution</strong>
              {text && <span className="m-inline-start-1">{text}</span>}
            </p>
          </Warning>
        </div>}
    </>;
};

export const LiveCode = ({children, customHeight, clickToLoad, example, fullWidth, fullHeight, hideCodeInLiveCode, screenshot, screenshotOnly, showCode: showCodeProp}) => {
  const SCREENSHOTS_BASE = "https://servicetitan.github.io/anvil2-docs-live-code/screenshots";
  const STACKBLITZ_BASE = "https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples";
  const [showCodeBlock, setShowCodeBlock] = useState(showCodeProp ?? false);
  const [isLocalOverride, setIsLocalOverride] = useState(false);
  useEffect(() => {
    const examplePath = `/images/live-code-screenshots-tmp/${example}.png`;
    fetch(examplePath, {
      method: "HEAD"
    }).then(r => {
      if (r.ok) setIsLocalOverride(true);
    }).catch(() => {});
  }, [example]);
  const screenshotBase = isLocalOverride ? "/images/live-code-screenshots-tmp" : SCREENSHOTS_BASE;
  if (screenshotOnly) {
    return <Frame className="flex flex-col">
        <div className="flex dark:hidden" style={{
      justifyContent: "center",
      alignItems: "center",
      width: fullWidth ? "100%" : "50%",
      minHeight: fullHeight ? "284px" : undefined,
      background: "#FFFFFF"
    }}>
          <img srcset={`${screenshotBase}/${example}.png, ${screenshotBase}/${example}-2x.png 2x`} src={`${screenshotBase}/${example}.png`} alt={example} noZoom />
        </div>
        <div className="hidden dark:flex" style={{
      justifyContent: "center",
      alignItems: "center",
      width: fullWidth ? "100%" : "50%",
      minHeight: fullHeight ? "284px" : undefined,
      background: "#141414"
    }}>
          <img srcset={`${screenshotBase}/${example}-dark.png, ${screenshotBase}/${example}-dark-2x.png 2x`} src={`${screenshotBase}/${example}-dark.png`} alt={example} noZoom />
        </div>
      </Frame>;
  }
  if (screenshot) {
    return <Frame className="flex flex-col -mb-2">
        <div className="flex dark:hidden bg-white dark:bg-codeblock border border-gray-950/10 dark:border-white/10 dark:twoslash-dark rounded-2xl overflow-hidden" style={{
      justifyContent: "center",
      alignItems: "center",
      width: fullWidth ? "100%" : "50%",
      minHeight: fullHeight ? "284px" : undefined
    }}>
          <img srcset={`${screenshotBase}/${example}.png, ${screenshotBase}/${example}-2x.png 2x`} src={`${screenshotBase}/${example}.png`} alt={example} noZoom />
        </div>

        <div className="hidden dark:flex bg-white dark:bg-codeblock border border-gray-950/10 dark:border-white/10 dark:twoslash-dark rounded-2xl overflow-hidden" style={{
      background: "#141414",
      justifyContent: "center",
      alignItems: "center",
      width: fullWidth ? "100%" : "50%",
      minHeight: fullHeight ? "284px" : undefined
    }}>
          <img srcset={`${screenshotBase}/${example}-dark.png, ${screenshotBase}/${example}-dark-2x.png 2x`} src={`${screenshotBase}/${example}-dark.png`} alt={example} noZoom />
        </div>

        <div className="flex justify-end items-center text-xs py-2 px-1 gap-4">
          {!showCodeProp ? <button className="inline-flex justify-end items-center text-gray-700 dark:text-gray-50 hover:text-blue-500 dark:hover:text-blue-300 transition-colors group self-end gap-1 cursor-pointer" onClick={() => setShowCodeBlock(!showCodeBlock)} style={{
      appearance: "none"
    }}>
              <Icon icon="code" size="12px" className="group-hover:bg-blue-500 dark:group-hover:bg-blue-300" />
              <span>{showCodeBlock ? "Hide code" : "Show code"}</span>
            </button> : null}

          <a className="inline-flex justify-end items-center hover:text-blue-500 dark:hover:text-blue-300 transition-colors group self-end gap-1" href={`${STACKBLITZ_BASE}/${example}?file=src/App.tsx`} target="_blank" rel="noreferrer">
            <Icon icon="bolt" size="12px" className="group-hover:bg-blue-500 dark:group-hover:bg-blue-300" />
            <span>StackBlitz demo</span>
          </a>
        </div>

        <div className="grid transition-[grid-template-rows] duration-300 ease-in-out overflow-auto overflow-y-hidden overflow-x-auto" style={showCodeBlock ? {
      gridTemplateRows: "1fr"
    } : {
      gridTemplateRows: "0fr"
    }}>
          <div style={{
      minHeight: 0,
      overflowX: "auto",
      overflowY: "hidden",
      marginBlockStart: "-1.25rem",
      marginBlockEnd: "-1.5rem"
    }}>
            {children}
          </div>
        </div>
      </Frame>;
  } else {
    return <div style={{
      display: "flex",
      width: fullWidth ? "100%" : "50%",
      minHeight: customHeight ? customHeight : "316px",
      resize: "vertical",
      overflow: "auto"
    }}>
        <iframe title={example} style={{
      flex: 1,
      width: fullWidth ? "100%" : "50%",
      minHeight: customHeight ? customHeight : "316px"
    }} src={`${STACKBLITZ_BASE}/${example}?embed=1&hideNavigation=1&hideExplorer=1&terminalHeight=0&file=src/App.tsx${clickToLoad ? "&ctl=1" : ""}${hideCodeInLiveCode ? "&view=preview" : ""}`} allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts" />
      </div>;
  }
};

<Frame>
  <div className="w-full h-full bg-[#FFFFFF] p-2 rounded flex items-center justify-center">
    <img noZoom src="https://mintcdn.com/servicetitan/740JwA2S4tZTQZ3H/images/docs/web/components/date-field-yearless/overview-image.png?fit=max&auto=format&n=740JwA2S4tZTQZ3H&q=85&s=691f788181d1c276638a2803f265dfc3" width="640" height="360" data-path="images/docs/web/components/date-field-yearless/overview-image.png" />
  </div>
</Frame>

## Anatomy

Date Field Yearless consists of five primary elements that enable users to select a month and day without year information.

<Frame>
  <div className="w-full h-full bg-[#FFFFFF] p-2 rounded flex items-center justify-center">
    <img src="https://mintcdn.com/servicetitan/66tZkkkyD48ugyZL/images/docs/web/components/date-field-yearless/design/date-field-yearless-anatomy.png?fit=max&auto=format&n=66tZkkkyD48ugyZL&q=85&s=6d47d3d8e4e3aa92aa7387d57674c49c" alt="Anatomy of the Date Field Yearless component" width="896" height="326" data-path="images/docs/web/components/date-field-yearless/design/date-field-yearless-anatomy.png" />
  </div>
</Frame>

1. **Label** - Identifies the field and its purpose
2. **Input field** - Text input for manual month and day entry with format mask
3. **Helper text** (Optional) - Description, format hint or error message

## Options

Date Field Yearless supports the following configurations to accommodate different use cases and regional preferences.

### Date Format Modes

<LiveCode example="datefieldyearless-mode" screenshot fullWidth>
  ```tsx lines theme={null}
  import { DateFieldYearless, Flex } from "@servicetitan/anvil2";

  function App() {
    return (
      <Flex direction="column" gap="4" style={{ minWidth: "384px" }}>
        {/** US format (default) */}
        <DateFieldYearless label="US Format" mode="mm/dd" />

        {/** International format */}
        <DateFieldYearless label="International Format" mode="dd/mm" />
      </Flex>
    );
  }

  export default App;
  ```
</LiveCode>

Date Field Yearless supports two date format modes to match regional conventions. The default format is `mm/dd` for US audiences, while `dd/mm` is available for international users.

| Mode  | Format   | Use Case                  |
| ----- | -------- | ------------------------- |
| mm/dd | Default  | US date format            |
| dd/mm | Optional | International date format |

### Sizes

<LiveCode example="datefieldyearless-size" screenshot fullWidth>
  ```tsx lines theme={null}
  import { DateFieldYearless, Flex } from "@servicetitan/anvil2";

  function App() {
    return (
      <Flex direction="column" gap="4" style={{ minWidth: "384px" }}>
        <DateFieldYearless label="Small" size="small" />
        <DateFieldYearless label="Medium" size="medium" />
        <DateFieldYearless label="Large" size="large" />
      </Flex>
    );
  }

  export default App;
  ```
</LiveCode>

Three size options accommodate different layout densities and visual hierarchies.

| Size   | Height |
| ------ | ------ |
| Small  | 32px   |
| Medium | 40px   |
| Large  | 48px   |

### Date Picker

<LiveCode example="datefieldyearless-disablepicker" screenshot fullWidth>
  ```tsx lines theme={null}
  import { DateFieldYearless } from "@servicetitan/anvil2";

  function App() {
    return (
      <DateFieldYearless
        label="Birthday"
        disablePicker
        description="Calendar picker is disabled, manual entry only"
      />
    );
  }

  export default App;
  ```
</LiveCode>

The date picker can be disabled when manual text entry is preferred or when the picker interface is not suitable for the use case.

## Behavior

Date Field Yearless responds to user interaction with distinct visual states and validation feedback.

### Visual States

<LiveCode example="datefieldyearless-visual-states" screenshot fullWidth>
  ```tsx lines theme={null}
  import { DateFieldYearless, Grid } from "@servicetitan/anvil2";

  function App() {
    return (
      <Grid gap="6">
        <DateFieldYearless
          label="Birthday"
          defaultValue={{ month: 7, day: 15 }}
          required
        />
        <DateFieldYearless
          label="Birthday"
          defaultValue={{ month: 7, day: 15 }}
          required
          disabled
        />
        <DateFieldYearless
          label="Birthday"
          defaultValue={{ month: 13, day: 32 }}
          required
          error="Please enter a valid date"
        />
      </Grid>
    );
  }

  export default App;
  ```
</LiveCode>

The component displays different visual states to communicate field status and interactivity. Default state shows the field ready for input. Focus state highlights the active field. Error state displays validation messages when dates are invalid or required fields are empty. Disabled state prevents interaction while maintaining visual context.

### Date Picker Interaction

Clicking the input field opens a popover with two scrollable lists for month and day selection. Users can scroll through months and days and select values directly from the lists. The picker closes automatically after selection, and the selected month and day populate the input field.

### Validation

<LiveCode example="datefieldyearless-isvalid" screenshot fullWidth>
  ```tsx lines theme={null}
  import {
    DateFieldYearless,
    Flex,
    type YearlessDate,
    type DateFieldYearlessChange,
  } from "@servicetitan/anvil2";
  import { useState } from "react";

  function App() {
    const [value, setValue] = useState<YearlessDate | null>();
    const [latestEvent, setLatestEvent] = useState<DateFieldYearlessChange>();
    const [errorMessage, setErrorMessage] = useState("");

    const handleChange = (change: DateFieldYearlessChange) => {
      setValue(change.value);
      setLatestEvent(change);
      if (change.isValid) setErrorMessage("");
    };

    const handleBlur = () => {
      if (!latestEvent) return;
      if (!latestEvent.isValid) return setErrorMessage("Invalid date");
      if (!latestEvent.isInputValid && !latestEvent.isInputEmpty)
        return setErrorMessage("Valid date, but input isn't fully filled");
    };
    return (
      <Flex direction="column">
        <DateFieldYearless
          label="Yearless validation example"
          description="Will error on New Year's day and leap day"
          value={value}
          onChange={handleChange}
          onBlur={handleBlur}
          error={errorMessage}
          unavailable={{
            dates: [
              { month: 1, day: 1 },
              { month: 2, day: 29 },
            ],
          }}
        />
        <pre>{JSON.stringify(latestEvent, null, 2)}</pre>
      </Flex>
    );
  }

  export default App;
  ```
</LiveCode>

Date Field Yearless validates input in real time. The component checks for parseable month and day values, required field completion, date constraints (min/max dates), and unavailable dates. Error messages appear below the field when validation fails.

## Usage Guidelines

### When to Use

Use Date Field Yearless when you need to:

* Capture recurring dates where year is irrelevant
* Select template dates for annual events
* Input birthdays or anniversaries
* Create seasonal date selections

### When not to use

Avoid using Date Field Yearless for:

* **Specific dates with year** - Use [Date Field Single](/docs/web/components/date-field-single/design) instead
* **Date ranges** - Use [Date Field Range](/docs/web/components/date-field-range/design) or [Date Field Yearless Range](/docs/web/components/date-field-yearless-range/design) instead
* **Time selection** - Use [Time Field](/docs/web/components/time-field/design) for time-only inputs

### Alternatives

#### Date Field Yearless vs Date Field Single

Use Date Field Yearless when the year is irrelevant to the date selection, such as recurring annual events or template dates. Use Date Field Single when the year is essential, such as appointment scheduling or deadline setting.

### How to Use

#### Labeling and Yearless Context

Provide clear, descriptive labels that communicate the purpose of the date selection and indicate that year information is not required. Labels should clarify that only month and day are needed, helping users understand why year input is omitted. This pattern simplifies input for recurring annual events.

<LiveCode example="datefieldyearless-birthday-selection" screenshot fullWidth>
  ```tsx lines theme={null}
  import { DateFieldYearless } from "@servicetitan/anvil2";

  function App() {
    return (
      <DateFieldYearless
        label="Recurring Date"
        description="Enter month and day only (year not required)"
        required
      />
    );
  }

  export default App;
  ```
</LiveCode>

<DoDont type="do" />

* Write labels that clearly communicate the purpose and indicate year is not needed
* Include format hints to guide users on expected input format (mm/dd or dd/mm)
* Set appropriate date constraints (min/max dates) when applicable to prevent invalid selections
* Display validation errors immediately when they occur
* Use Date Field Yearless for recurring dates where year context is not needed
* Maintain consistency when using yearless dates alongside year-specific dates in forms

#### Anti-pattern: Ambiguous Context and Mixed Patterns

Avoid creating confusion by using Date Field Yearless for dates that require year information or mixing yearless and year-specific patterns without clear distinction.

<LiveCode example="datefieldyearless-specific-dates-dont" screenshot fullWidth>
  ```tsx lines theme={null}
  import { DateFieldYearless } from "@servicetitan/anvil2";

  function App() {
    return <DateFieldYearless label="Deadline" required />;
  }

  export default App;
  ```
</LiveCode>

<DoDont type="dont" />

* Use Date Field Yearless for dates that require year information
* Omit format hints when the date format might be ambiguous to users
* Hide validation errors until form submission
* Use placeholder text to communicate date format instead of format hints
* Mix yearless dates with year-specific dates in the same form without clear visual or contextual distinction
* Create labels that don't indicate year is not required

## Content

Content within Date Field Yearless should be clear, concise, and actionable.

* Labels should clearly indicate what date is being selected and that year is not required
* Error messages should explain why a date is invalid and how to fix it
* Format hints should match the selected mode (mm/dd or dd/mm)
* Descriptions should provide context without overwhelming the user

## Keyboard Interaction

Users can navigate Date Field Yearless using standard keyboard controls.

### Input Field

| Key         | Description                         |
| ----------- | ----------------------------------- |
| Tab         | Moves focus to the next element     |
| Shift + Tab | Moves focus to the previous element |
| Arrow Down  | Opens the date picker when focused  |
| Esc         | Closes the date picker              |

### Date Picker

| Key        | Description                                  |
| ---------- | -------------------------------------------- |
| Arrow Up   | Moves focus to the previous item in the list |
| Arrow Down | Moves focus to the next item in the list     |
| Esc        | Closes the date picker                       |

### Accessibility

Date Field Yearless includes built-in accessibility features. The component supports full keyboard navigation and screen reader compatibility. Ensure labels are descriptive and error messages are clear for assistive technology users.

For more guidance on form field labels and context, see [input field context association best practices](/docs/accessibility/labels-and-ctas#input-field-context-association).
