> ## 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.

# Filter Bar – Code

> Standalone filter bar component with filtering controls

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>;
  }
};

<Tabs>
  <Tab title="Implementation">
    <Note>
      **Beta Feature**

      This feature is currently in beta, and needs to be imported from `@servicetitan/anvil2/beta`.

      While we hope to minimize breaking changes, they may occur due to feedback we receive or other improvements. These will always be documented in the changelog and communicated in Slack.

      Please reach out in the [#ask-designsystem](https://servicetitan.enterprise.slack.com/archives/CBSRGHTRS) channel with any questions or feedback!
    </Note>

    ## Common Examples

    ### Standard Filters

    ```tsx theme={null}
    import { FilterBar } from "@servicetitan/anvil2/beta";

    function ExampleComponent() {
      const filters = [
        // See filter objects below
      ];

      return (
        <FilterBar
          associatedContent="..."
          filters={filters}
          onFilterChange={(updatedFilters) => console.log(updatedFilters)}
        />
      );
    }
    ```

    A filter group is created by passing a `filters` object array to the `FilterBar` component. The component takes the object and builds the set of filters for you according to the design patterns. You can create a custom filter if needed, but there are currently 5 available prebuilt filter types to pick from:

    #### Boolean

    ```tsx theme={null}
    {
      type: "boolean",
      id: "booleanFilterId",
      label: "Boolean Filter",
      checked: false,
    }
    ```

    #### Single Select

    The item list for single select is populated and controlled by the implementing team. This includes any filtering that needs to be done when a search field is present.

    `Simple Example`

    ```tsx theme={null}
    {
      type: "singleSelect",
      id: "singleSelectFilterId",
      label: "Single Select Filter",
      items: [
        {
          id: "singleOption1",
          label: "Single Option 1"
        },
        ...
      ],
      simpleDrawerVariant
    }
    ```

    `With Search Example`

    Since population of the item list is controlled by implementing team, the Single Select search handler needs to have an empty search term conditional in it so the filter drawer draft state can properly reset the displayed items after selection but before applying the batch update.

    ```tsx theme={null}
    {
      type: "singleSelect",
      id: "singleSelectFilterId",
      label: "Single Select Filter",
      items: [
        {
          id: "singleOption1",
          label: "Single Option 1"
        },
        ...
      ],
      hasSearch: true,
      onSearch: (searchTerm) => {
        if (!searchTerm) {
          setSingleSelectOptions(statusOptions);
        } else {
          const filtered = statusOptions.filter((option) =>
            option.label.toLowerCase().includes(searchTerm.toLowerCase()));
          setSingleSelectOptions(filtered);
        }
      },
      onSearchClear: handleMultiselectSearchClear,
    }
    ```

    #### Multi Select

    The item list for single select is populated and controlled by the implementing team. This includes any filtering that needs to be done when a search field is present.

    `Simple Example`

    ```tsx theme={null}
    {
      type: "multiSelect",
      id: "multiSelectFilterId",
      label: "Multi Select Filter",
      items: [
        {
          id: "multiOption1",
          label: "Multi Option 1"
        },
        ...
      ],
      simpleDrawerVariant
    }
    ```

    `With Search Example`

    Since population of the item list is controlled by implementing team, the Single Select search handler needs to have an empty search term conditional in it so the filter drawer draft state can properly reset the displayed items after selection but before applying the batch update.

    ```tsx theme={null}
    {
      type: "multiSelect",
      id: "multiSelectFilterId",
      label: "Multi Select Filter",
      items: [
        {
          id: "multiOption1",
          label: "Multi Option 1"
        },
        ...
      ],
      selectedItems: [],
      hasSearch: true,
      onSearch: (searchTerm) => {
        if (!searchTerm) {
          setMultiSelectOptions(statusOptions);
        } else {
          const filtered = statusOptions.filter((option) =>
            option.label.toLowerCase().includes(searchTerm.toLowerCase()));
          setMultiSelectOptions(filtered);
        }
      },,
      onSearchClear: handleMultiselectSearchClear,
    }
    ```

    #### Single Date Selection

    ```tsx theme={null}
    {
      type: "date",
      id: "dateFilterId",
      label: "Date Filter",
      mode: "mm/dd/yyyy",
    }
    ```

    #### Range Date Selection

    ```tsx theme={null}
    {
      type: "dateRange",
      id: "dateRangeFilterId",
      label: "Date Range Filter",
      mode: "mm/dd/yyyy",
    }
    ```

    These filter types come with preset filter drawer variants.

    ### Custom Filters

    ```tsx theme={null}
    import { FilterBar } from "@servicetitan/anvil2/beta";

    function ExampleComponent() {
      const [labelText, setLabelText] = useState("");

      const buttonComponent = ({ value, onChange }) => (
        <YourComponentHere value={value} onChange={onChange} />
      ))};

      const drawerComponent = ({ value, onChange }) => (
        <YourComponentHere value={value} onChange={onChange} />
      ))};

      const filters = [
        {
          type: "custom",
          id: "customFilterId",
          label: labelText,
          buttonRender: buttonComponent,
          drawerRender: renderComponent
        }
      ];

      return(
        <FilterBar
          associatedContent="..."
          filters={filters}
          onFilterChange={
            (updateFilters) => {
              console.log(updateFilters);
              setLabelText(....);
            }
          }
        />
      )
    }
    ```

    Provide a `buttonRender`, `drawerRender`, and custom `label` to create a custom filter. Label's for custom filters can either be a string or a string with a chip count. Custom filters can be used by themselves or as part of a combined filter array.

    ### Filters with Apply/Cancel Triggers

    ```tsx theme={null}
    import { FilterBar } from "@servicetitan/anvil2/beta";

    function ExampleComponent() {
      const filters = [
        ...
      ];

      return (
        <FilterBar
          associatedContent="..."
          filters={filters}
          onFilterChange={
            (updatedFilters) => console.log(updatedFilters)
          }
          controlledFiltering
        />
      )
    }
    ```

    By default, filters update as soon as a selection is made in the filter button. When `controlledFiltering` is set to true, Apply and Cancel buttons appear at the bottom of single select, multi select, single date selection, range date selection, and custom filter button popovers to control updating. This setting has no impact on the Filter Drawer, which always submits as a batch update upon clicking Apply.

    ### Filters with Search Field

    ```tsx theme={null}
    import { FilterBar } from "@servicetitan/anvil2/beta";
    import { Toolbar } from "@servicetitan/anvil2/beta";
    import { Flex } from "@servicetitan/anvil2";

    function ExampleComponent() {
      const filters = [
        ...
      ];

      const handleToolbarSearch = (e) => {
        ...
      }

      const handleToolbarSearchClear = () => {
        ...
      }

      return (
        <Flex direction="column" gap={2}>
          <Toolbar associatedContent="...">
            <Toolbar.Search
              placeholder="Search..."
              onChange={handleToolbarSearch}
              onClear={handleToolbarSearchClear}
            />
          </Toolbar>
          <FilterBar
            associatedContent="..."
            filters={filters}
            onFilterChange={
              (updatedFilters) => console.log(updatedFilters)
            }
          />
        </Flex>
      )
    }
    ```

    Use a `Toolbar.Search` alongside `FilterBar` to include a search input with your filters. The search bar works independently from the filters themselves and has its own search props.

    ### Filters with Additional Toolbar Items

    ```tsx theme={null}
    import { FilterBar } from "@servicetitan/anvil2/beta";
    import { Toolbar } from "@servicetitan/anvil2/beta";
    import { Flex } from "@servicetitan/anvil2";

    function ExampleComponent() {
      const filters = [
        ...
      ];

      return (
        <Flex direction="column" gap={2}>
          <FilterBar
            associatedContent="..."
            filters={filters}
            onFilterChange={
              (updatedFilters) => console.log(updatedFilters)
            }
          />
          <Toolbar associatedContent="...">
            <Toolbar.ControlGroup>
              <Toolbar.Button
                icon={{ before: AttachFile }}
                aria-label="File button"
                ...
              />
              // other base toolbar controls
            </Toolbar.ControlGroup>
          </Toolbar>
        </Flex>
      )
    }
    ```

    Use `FilterBar` alongside a `Toolbar` with [Toolbar](/docs/web/components/toolbar/design) control options to have additional actions. The `FilterBar` and `Toolbar` are rendered as siblings and can be composed with `Flex` for layout.

    ### Overflow Modes

    ```tsx theme={null}
    import { FilterBar } from "@servicetitan/anvil2/beta";

    function ExampleComponent() {
      const filters = [
        ...
      ];

      return (
        <>
          // Default wrap mode
          <FilterBar
            associatedContent="..."
            filters={filters}
            onFilterChange={
              (updatedFilters) => console.log(updatedFilters)
            }
          />

          // Collapsed overflow mode
          <FilterBar
            associatedContent="..."
            overflow="collapse"
            filters={filters}
            onFilterChange={
              (updatedFilters) => console.log(updatedFilters)
            }
          />
        </>
      )
    }
    ```

    The `FilterBar` overflow mode defaults to `wrap`. Set `overflow="collapse"` to collapse filters that do not fit within the available space.

    In `collapse` mode, a hidden filter does not get added to an overflow menu, it is only removed from the screen. All filters remain accessible through the filter drawer in any mode.

    ## React Accessibility

    ### Aria Labels

    Add an `associatedContent` prop to the `FilterBar` for proper group identification for screen readers. The `associatedContent` value provides a11y context, generating labels like "Filters for {associatedContent}".
  </Tab>

  <Tab title="FilterBar Props">
    ```tsx theme={null}
    import { FilterBar } from "@servicetitan/anvil2/beta";

    function ExampleComponent() {
    return (

    <FilterBar
      associatedContent="..."
      filters={filters}
      controlledFiltering={false}
      onFilterChange={(filters) => console.log(filters)}
    />
    ); }

    ```

    ## `FilterBar` Props

    <ParamField path="associatedContent" type="string" required>
      Describes the content being filtered for accessibility context. Used to generate aria-labels.
    </ParamField>

    <ParamField path="filters" type="Filter[]" required>
      Union type of all available filter objects.
    </ParamField>

    <ParamField path="onFilterChange" type="(filters: Filter[]) => void" required>
      Callback function for when filters change.
    </ParamField>

    <ParamField path="controlledFiltering" type="boolean" default="false">
      When enabled, filter button submission is controlled via an apply button.
    </ParamField>

    <ParamField path="overflow" type={`"wrap" | "collapse"`} default="wrap">
      Controls how filters behave when they exceed the available horizontal space.
    </ParamField>

    <ParamField path="size" type={`"xsmall" | "small" | "medium" | "large"`} default="xsmall">
      Controls the size of the filter bar and its filter buttons.
    </ParamField>
  </Tab>

  <Tab title="Boolean Filter Props">
    ```tsx theme={null}
    {
      type: "boolean",
      id: "booleanFilterId",
      label: "Boolean Filter",
      checked: false,
    }
    ```

    ## Boolean Filter Props

    To have a boolean filter, you must have `type: "boolean"` in the filter object in the array.

    <ParamField path="type" type="boolean">
      To have a boolean filter, this type must be in the object.
    </ParamField>

    <ParamField path="checked" type="boolean" default="false">
      Selected state of the filter
    </ParamField>

    <ParamField path="id" type="string">
      Unique identifier for filter
    </ParamField>

    <ParamField path="label" type="string">
      Label for filter
    </ParamField>
  </Tab>

  <Tab title="Single Select Filter Props">
    ```tsx theme={null}
    {
      type: "singleSelect",
      id: "singleSelectFilterId",
      label: "Single Select Filter",
      items: [{ id: "option1", label: "Option 1" }],
      hasSearch: false,
      simpleDrawerVariant: false,
    }
    ```

    ## Single Select Filter Props

    To have a single select filter, you must have `type: "singleSelect"` in the filter object in the array.

    <ParamField path="type" type="singleSelect">
      Identifies this as a single select filter
    </ParamField>

    <ParamField path="id" type="string">
      Unique identifier for the filter
    </ParamField>

    <ParamField path="label" type="string">
      Display label for the filter
    </ParamField>

    <ParamField path="items" type="Item[]">
      Array of items to select from. The `Item` type defaults to `{ id: string; label: string }` but can be extended with custom properties as needed.
    </ParamField>

    <ParamField path="hasSearch" type="boolean" default="false">
      Whether to show the search field
    </ParamField>

    <ParamField path="onSearch" type="(searchTerm: string) => void">
      Callback for search functionality (only used when hasSearch is true)
    </ParamField>

    <ParamField path="onSearchClear" type="() => void">
      Callback when search is cleared (only used when hasSearch is true)
    </ParamField>

    <ParamField path="selectedItem" type="Item">
      Currently selected item
    </ParamField>

    <ParamField path="simpleDrawerVariant" type="boolean" default="false">
      Whether to use simplified drawer rendering (not available when hasSearch is
      true)
    </ParamField>
  </Tab>

  <Tab title="Multi Select Filter Props">
    ```tsx theme={null}
    {
      type: "multiSelect",
      id: "multiSelectFilterId",
      label: "Multi Select Filter",
      items: [{ id: "option1", label: "Option 1" }],
      selectedItems: [],
      hasSearch: false,
      simpleDrawerVariant: false,
    }
    ```

    ## Multi Select Filter Props

    To have a multi select filter, you must have `type: "multiSelect"` in the filter object in the array.

    <ParamField path="type" type="multiSelect">
      Identifies this as a multi select filter
    </ParamField>

    <ParamField path="id" type="string">
      Unique identifier for the filter
    </ParamField>

    <ParamField path="label" type="string">
      Display label for the filter
    </ParamField>

    <ParamField path="items" type="Item[]">
      Array of items to select from. The `Item` type defaults to `{ id: string; label: string }` but can be extended with custom properties as needed.
    </ParamField>

    <ParamField path="selectedItems" type="Item[]" default="[]">
      Currently selected items array
    </ParamField>

    <ParamField path="hasSearch" type="boolean" default="false">
      Whether to show the search field
    </ParamField>

    <ParamField path="onSearch" type="(searchTerm: string) => void">
      Callback for search functionality (only used when hasSearch is true)
    </ParamField>

    <ParamField path="onSearchClear" type="() => void">
      Callback when search is cleared (only used when hasSearch is true)
    </ParamField>

    <ParamField path="simpleDrawerVariant" type="boolean" default="false">
      Whether to use simplified drawer rendering (not available when hasSearch is
      true)
    </ParamField>
  </Tab>

  <Tab title="Single Date Selection Filter Props">
    ```tsx theme={null}
    {
      type: "date",
      id: "dateFilterId",
      label: "Date Filter",
      mode: "mm/dd/yyyy",
      value: null,
    }
    ```

    ## Single Date Selection Filter Props

    To have a single date selection filter, you must have `type: "date"` in the filter object in the array.

    <ParamField path="type" type="date">
      Identifies this as a single date filter
    </ParamField>

    <ParamField path="id" type="string">
      Unique identifier for the filter
    </ParamField>

    <ParamField path="label" type="string">
      Display label for the filter
    </ParamField>

    <ParamField path="mode" type={`"mm/dd/yyyy" | "dd/mm/yyyy"`} default="mm/dd/yyyy">
      Date format for the input.
    </ParamField>

    <ParamField path="value" type="Date | null" default="null">
      Currently selected date value.
    </ParamField>
  </Tab>

  <Tab title="Ranged Date Selection Filter Props">
    ```tsx theme={null}
    {
      type: "dateRange",
      id: "dateRangeFilterId",
      label: "Date Range Filter",
      mode: "mm/dd/yyyy",
      value: { start: null, end: null },
    }
    ```

    ## Ranged Date Selection Filter Props

    To have a ranged date selection filter, you must have `type: "dateRange"` in the filter object in the array.

    <ParamField path="type" type="dateRange">
      Identifies this as a date range filter
    </ParamField>

    <ParamField path="id" type="string">
      Unique identifier for the filter
    </ParamField>

    <ParamField path="label" type="string">
      Display label for the filter
    </ParamField>

    <ParamField path="mode" type={`"mm/dd/yyyy" | "dd/mm/yyyy"`} default="mm/dd/yyyy">
      Date format for the inputs.
    </ParamField>

    <ParamField path="value" type="{ start: Date | null; end: Date | null }" default="{ start: null, end: null }">
      Currently selected date range.
    </ParamField>
  </Tab>

  <Tab title="Custom Filter Props">
    ```tsx theme={null}
    {
      type: "custom",
      id: "customFilterId",
      label: "Custom Filter",
      buttonRender: ({ value, onChange }) => <Component />,
      drawerRender: ({ value, onChange }) => <Component />,
      value: null,
      labelChipCount: 0,
    }
    ```

    ## Custom Filter Props

    To have a custom filter, you must have `type: "custom"` in the filter object in the array.

    <ParamField path="type" type="custom">
      Identifies this as a custom filter
    </ParamField>

    <ParamField path="id" type="string">
      Unique identifier for the filter
    </ParamField>

    <ParamField path="label" type="string">
      Display label for the filter
    </ParamField>

    <ParamField path="buttonRender" type="(props: { value?: T; onChange: (value?: T) => void }) => ReactNode">
      Function to render the filter content in the button/popover
    </ParamField>

    <ParamField path="drawerRender" type="(props: { value?: T; onChange: (value?: T) => void }) => ReactNode">
      Function to render the filter content in the drawer
    </ParamField>

    <ParamField path="labelChipCount" type="number">
      Optional number to display in a label chip
    </ParamField>

    <ParamField path="value" type="T">
      Current value of the custom filter
    </ParamField>
  </Tab>
</Tabs>
