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

# Empty States

> Empty states are messages that appear whenever a page or element has no content to display to the user. An empty state is an opportunity to engage and delight users. The empty state should tell users what it’s for and why they’re seeing it. Effective empty states also tell users what they can do next.

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

# Overview

<LiveCode example="empty-state-overview" screenshot fullWidth>
  ```tsx lines theme={null}
  import { useState, useEffect } from "react";
  import { Text, Flex, Button } from "@servicetitan/anvil2";
  import emptyStateImageLight from "../assets/empty_state-first_use-light.png";
  import emptyStateImageDark from "../assets/empty_state-first_use-dark.png";

  function App() {
    // Temp until usePrefersColorScheme is fixed
    const [isDark, setIsDark] = useState(
      () => window.matchMedia("(prefers-color-scheme: dark)").matches,
    );

    useEffect(() => {
      const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
      const handler = (e: MediaQueryListEvent) => setIsDark(e.matches);
      mediaQuery.addEventListener("change", handler);
      return () => mediaQuery.removeEventListener("change", handler);
    }, []);
    // temp end

    const emptyStateImage = isDark ? emptyStateImageDark : emptyStateImageLight;
    return (
      <Flex
        alignItems="center"
        justifyContent="center"
        direction="column"
        gap={6}
        style={{ maxWidth: "420px" }}
      >
        <Flex alignItems="center" direction="column">
          <img
            src={emptyStateImage}
            alt="First-time setup illustration"
            height={200}
          />

          <Text variant="headline" el="h2">
            Looks like Reports haven&apos;t been set up yet
          </Text>
          <Text style={{ textAlign: "center" }}>
            Set it up today to start improving your business through our
            integrated system.
          </Text>
        </Flex>
        <Button appearance="primary">Start Setup</Button>
      </Flex>
    );
  }

  export default App;
  ```
</LiveCode>

## Resources

<Card title="Empty State Spot Illustrations" icon="figma" href="https://www.figma.com/design/A4RgCr9ds9au8yBq8LhUhT/Empty-State-Spot-Illustrations">
  Figma library with empty state illustrations
</Card>

Empty states can be employed to:

* Make users aware that a feature is not configured for use.
* Communicate that there is no data to display.
* Act as a placeholder for regions in the app left blank intentionally.
* Convey an error state, like a server response error, i.e., 404 or 500.
* Display empty search results.
* Help users onboard and encourage them to interact with the application
* Prevent users from having to “back up” and go in a different direction

## Headlines

Empty states headlines should be proactive and helpful. Focus on the task ahead that will help them fill the empty state, rather than calling out what’s missing from the screen. Calling out what’s missing can work in description text, but it’s always better to give information about the filled state of the screen.

<LiveCode example="empty-state-headline-do" screenshot fullWidth>
  ```tsx lines theme={null}
  import { useState, useEffect } from "react";
  import { Text, Flex, Button } from "@servicetitan/anvil2";
  import emptyStateImageLight from "../assets/empty_state-no_search_results-light.png";
  import emptyStateImageDark from "../assets/empty_state-no_search_results-dark.png";

  function App() {
    // Temp until usePrefersColorScheme is fixed
    const [isDark, setIsDark] = useState(
      () => window.matchMedia("(prefers-color-scheme: dark)").matches,
    );

    useEffect(() => {
      const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
      const handler = (e: MediaQueryListEvent) => setIsDark(e.matches);
      mediaQuery.addEventListener("change", handler);
      return () => mediaQuery.removeEventListener("change", handler);
    }, []);
    // temp end

    const emptyStateImage = isDark ? emptyStateImageDark : emptyStateImageLight;
    return (
      <Flex
        alignItems="center"
        justifyContent="center"
        direction="column"
        gap={6}
        style={{ maxWidth: "420px" }}
      >
        <Flex alignItems="center" direction="column">
          <img src={emptyStateImage} alt="No results illustration" height={200} />

          <Text variant="headline" el="h2">
            Bookmark Your Reports
          </Text>
          <Text style={{ textAlign: "center" }}>
            Bookmarked reports will appear here for quick access.
          </Text>
        </Flex>
        <Button>Learn More</Button>
      </Flex>
    );
  }

  export default App;
  ```
</LiveCode>

<Check>**Do**</Check>

<LiveCode example="empty-state-headline-dont" screenshot fullWidth>
  ```tsx lines theme={null}
  import { useState, useEffect } from "react";
  import { Text, Flex, Button } from "@servicetitan/anvil2";
  import emptyStateImageLight from "../assets/empty_state-no_search_results-light.png";
  import emptyStateImageDark from "../assets/empty_state-no_search_results-dark.png";

  function App() {
    // Temp until usePrefersColorScheme is fixed
    const [isDark, setIsDark] = useState(
      () => window.matchMedia("(prefers-color-scheme: dark)").matches,
    );

    useEffect(() => {
      const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
      const handler = (e: MediaQueryListEvent) => setIsDark(e.matches);
      mediaQuery.addEventListener("change", handler);
      return () => mediaQuery.removeEventListener("change", handler);
    }, []);
    // temp end

    const emptyStateImage = isDark ? emptyStateImageDark : emptyStateImageLight;
    return (
      <Flex
        alignItems="center"
        justifyContent="center"
        direction="column"
        gap={6}
        style={{ maxWidth: "420px" }}
      >
        <Flex alignItems="center" direction="column">
          <img src={emptyStateImage} alt="No results illustration" height={200} />

          <Text variant="headline" el="h2">
            No Bookmarks
          </Text>
          <Text style={{ textAlign: "center" }}>
            You haven&apos;t bookmarked any reports yet.
          </Text>
        </Flex>
        <Button>Learn More</Button>
      </Flex>
    );
  }

  export default App;
  ```
</LiveCode>

<Danger>**Don't**</Danger>

## Compared to Banners

Empty states differ from banners in that they are not intended to promote or advertise new features or functionality.

<LiveCode example="empty-state-compare-to-banner-do" screenshot fullWidth>
  ```tsx lines theme={null}
  import { useState, useEffect } from "react";
  import { Text, Flex, Button } from "@servicetitan/anvil2";
  import emptyStateImageLight from "../assets/empty_state-first_use-light.png";
  import emptyStateImageDark from "../assets/empty_state-first_use-dark.png";

  function App() {
    // Temp until usePrefersColorScheme is fixed
    const [isDark, setIsDark] = useState(
      () => window.matchMedia("(prefers-color-scheme: dark)").matches,
    );

    useEffect(() => {
      const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
      const handler = (e: MediaQueryListEvent) => setIsDark(e.matches);
      mediaQuery.addEventListener("change", handler);
      return () => mediaQuery.removeEventListener("change", handler);
    }, []);
    // temp end

    const emptyStateImage = isDark ? emptyStateImageDark : emptyStateImageLight;
    return (
      <Flex
        alignItems="center"
        justifyContent="center"
        direction="column"
        gap={6}
        style={{ maxWidth: "420px" }}
      >
        <Flex alignItems="center" direction="column">
          <img
            src={emptyStateImage}
            alt="First-time setup illustration"
            height={200}
          />

          <Text variant="headline" el="h2">
            Looks like Reports haven&apos;t been set up yet
          </Text>
          <Text style={{ textAlign: "center" }}>
            Set it up today to start improving your business through our
            integrated system.
          </Text>
        </Flex>
        <Button appearance="primary">Start Setup</Button>
      </Flex>
    );
  }

  export default App;
  ```
</LiveCode>

<Check>**Do**</Check>

<LiveCode example="empty-state-compare-to-banner-dont" screenshot fullWidth>
  ```tsx lines theme={null}
  import { useState, useEffect } from "react";
  import { Text, Flex, Button } from "@servicetitan/anvil2";
  import emptyStateImageLight from "../assets/empty_state-first_use-light.png";
  import emptyStateImageDark from "../assets/empty_state-first_use-dark.png";

  function App() {
    // Temp until usePrefersColorScheme is fixed
    const [isDark, setIsDark] = useState(
      () => window.matchMedia("(prefers-color-scheme: dark)").matches,
    );

    useEffect(() => {
      const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
      const handler = (e: MediaQueryListEvent) => setIsDark(e.matches);
      mediaQuery.addEventListener("change", handler);
      return () => mediaQuery.removeEventListener("change", handler);
    }, []);
    // temp end

    const emptyStateImage = isDark ? emptyStateImageDark : emptyStateImageLight;
    return (
      <Flex
        alignItems="center"
        justifyContent="center"
        direction="column"
        gap={6}
        style={{ maxWidth: "420px" }}
      >
        <Flex alignItems="center" direction="column">
          <img
            src={emptyStateImage}
            alt="First-time setup illustration"
            height={200}
          />

          <Text variant="headline" el="h2">
            Get Reports
          </Text>
          <Text style={{ textAlign: "center" }}>
            Drive higher booking rates, increase revenue, and boost efficiency
            with a fully-integrated, cloud-based report system.
          </Text>
        </Flex>
        <Button appearance="primary">Upgrade Now</Button>
      </Flex>
    );
  }

  export default App;
  ```
</LiveCode>

<Danger>**Don't**</Danger>

## Compared to Introduction Screens

Empty states differ from introduction screens in the headline wording. They should give the user an instruction. Additionally, introduction screens will often be the first screen after launching a wizard, whereas an empty state would not likely be used in that scenario.

<LiveCode example="empty-state-compare-to-introduction-do" screenshot fullWidth>
  ```tsx lines theme={null}
  import { useState, useEffect } from "react";
  import { Text, Flex, Button } from "@servicetitan/anvil2";
  import emptyStateImageLight from "../assets/empty_state-first_use-light.png";
  import emptyStateImageDark from "../assets/empty_state-first_use-dark.png";

  function App() {
    // Temp until usePrefersColorScheme is fixed
    const [isDark, setIsDark] = useState(
      () => window.matchMedia("(prefers-color-scheme: dark)").matches,
    );

    useEffect(() => {
      const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
      const handler = (e: MediaQueryListEvent) => setIsDark(e.matches);
      mediaQuery.addEventListener("change", handler);
      return () => mediaQuery.removeEventListener("change", handler);
    }, []);
    // temp end

    const emptyStateImage = isDark ? emptyStateImageDark : emptyStateImageLight;
    return (
      <Flex
        alignItems="center"
        justifyContent="center"
        direction="column"
        gap={6}
        style={{ maxWidth: "420px" }}
      >
        <Flex alignItems="center" direction="column">
          <img
            src={emptyStateImage}
            alt="First-time setup illustration"
            height={200}
          />

          <Text variant="headline" el="h2">
            Set Up Your Reports
          </Text>
          <Text style={{ textAlign: "center" }}>
            You can now create reports with rules based on your business needs. No
            more complex spreadsheets just to keep track of your business health.
          </Text>
        </Flex>
        <Button appearance="primary">Get Started</Button>
      </Flex>
    );
  }

  export default App;
  ```
</LiveCode>

<Check>**Do**</Check>

<LiveCode example="empty-state-compare-to-introduction-dont" screenshot fullWidth>
  ```tsx lines theme={null}
  import { useState, useEffect } from "react";
  import { Text, Flex, Button } from "@servicetitan/anvil2";
  import emptyStateImageLight from "../assets/empty_state-first_use-light.png";
  import emptyStateImageDark from "../assets/empty_state-first_use-dark.png";

  function App() {
    // Temp until usePrefersColorScheme is fixed
    const [isDark, setIsDark] = useState(
      () => window.matchMedia("(prefers-color-scheme: dark)").matches,
    );

    useEffect(() => {
      const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
      const handler = (e: MediaQueryListEvent) => setIsDark(e.matches);
      mediaQuery.addEventListener("change", handler);
      return () => mediaQuery.removeEventListener("change", handler);
    }, []);
    // temp end

    const emptyStateImage = isDark ? emptyStateImageDark : emptyStateImageLight;
    return (
      <Flex
        alignItems="center"
        justifyContent="center"
        direction="column"
        gap={6}
        style={{ maxWidth: "420px" }}
      >
        <Flex alignItems="center" direction="column">
          <img
            src={emptyStateImage}
            alt="First-time setup illustration"
            height={200}
          />

          <Text variant="headline" el="h2">
            Welcome to Your Reports Walkthrough
          </Text>
          <Text style={{ textAlign: "center" }}>
            We will now teach you how to create reports with rules base don your
            business needs. This walkthrough will take 5-10 minutes.
          </Text>
        </Flex>
        <Button appearance="primary">Get Started</Button>
      </Flex>
    );
  }

  export default App;
  ```
</LiveCode>

<Danger>**Don't**</Danger>

# Types of Empty States

## First Time Setup

During the initial onboarding process, the user will encounter a lot of empty states. We should try to make these empty states fun and encouraging with an approachable tone and helpful content so the user feels supported and confident through the set up process. Since these empty states will only be seen once or twice, they can be celebratory and decorative without being overkill.

### Educate the User

Sometimes the purpose of the empty state is to teach the user how to fill it by explaining what to do next or using a CTA to link them to a walk-through. Once the user learns how to fill the screen, there’s a good chance it may not be empty again. If it is likely to be empty again, an additional Recurring Empty State may be needed.

* Keep explanations concise. Any longer than 200 characters and/or 4 lines of text should be made into a help article or walk-through.
* If linking to a help article or walk-through, be sure to prime the user on what each will entail.
* Illustrations are encouraged. See Illustrations for more information on how to employ them.
* Keep the tone friendly and encouraging.

<LiveCode example="empty-state-type-first-time-education" screenshot fullWidth>
  ```tsx lines theme={null}
  import { useState, useEffect } from "react";
  import { Text, Flex } from "@servicetitan/anvil2";
  import emptyStateImageLight from "../assets/empty_state-first_use-light.png";
  import emptyStateImageDark from "../assets/empty_state-first_use-dark.png";

  function App() {
    // Temp until usePrefersColorScheme is fixed
    const [isDark, setIsDark] = useState(
      () => window.matchMedia("(prefers-color-scheme: dark)").matches,
    );

    useEffect(() => {
      const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
      const handler = (e: MediaQueryListEvent) => setIsDark(e.matches);
      mediaQuery.addEventListener("change", handler);
      return () => mediaQuery.removeEventListener("change", handler);
    }, []);
    // temp end

    const emptyStateImage = isDark ? emptyStateImageDark : emptyStateImageLight;
    return (
      <Flex
        alignItems="center"
        justifyContent="center"
        direction="column"
        gap={6}
        style={{ maxWidth: "420px" }}
      >
        <Flex alignItems="center" direction="column">
          <img
            src={emptyStateImage}
            alt="First-time setup illustration"
            height={200}
          />

          <Text variant="headline" el="h2">
            Build a Custom Report
          </Text>
          <Text style={{ textAlign: "center" }}>
            Click &quot;Add Filter&quot; in the left column to include or exclude
            specific characteristics of your audience. You can nest segment logic
            and have as many segments as you need.
          </Text>
        </Flex>
      </Flex>
    );
  }

  export default App;
  ```
</LiveCode>

<LiveCode example="empty-state-type-first-time-education+cta" screenshot fullWidth>
  ```tsx lines theme={null}
  import { useState, useEffect } from "react";
  import { Text, Flex, Button } from "@servicetitan/anvil2";
  import emptyStateImageLight from "../assets/empty_state-first_use-light.png";
  import emptyStateImageDark from "../assets/empty_state-first_use-dark.png";

  function App() {
    // Temp until usePrefersColorScheme is fixed
    const [isDark, setIsDark] = useState(
      () => window.matchMedia("(prefers-color-scheme: dark)").matches,
    );

    useEffect(() => {
      const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
      const handler = (e: MediaQueryListEvent) => setIsDark(e.matches);
      mediaQuery.addEventListener("change", handler);
      return () => mediaQuery.removeEventListener("change", handler);
    }, []);
    // temp end

    const emptyStateImage = isDark ? emptyStateImageDark : emptyStateImageLight;
    return (
      <Flex
        alignItems="center"
        justifyContent="center"
        direction="column"
        gap={6}
        style={{ maxWidth: "420px" }}
      >
        <Flex alignItems="center" direction="column">
          <img
            src={emptyStateImage}
            alt="First-time setup illustration"
            height={200}
          />

          <Text variant="headline" el="h2">
            Build a Custom Report
          </Text>
          <Text style={{ textAlign: "center" }}>
            Not sure how to get started? Let us take you through a quick
            walk-through.
          </Text>
        </Flex>
        <Button>Learn More</Button>
      </Flex>
    );
  }

  export default App;
  ```
</LiveCode>

### Quick Setup

Sometimes the purpose of the empty state is to bring the user through a setup flow as quickly as possible. Once the user completes the setup flow, there’s a good chance the empty state will never show up again. If it is likely to be empty again, an additional Recurring Empty State may be needed.

* Use a clear headline and CTA to initiate the setup flow.
* Make sure to inform the user about what info/documents they need on hand to complete the setup.
* Illustrations are encouraged.
* Keep the tone friendly and encouraging.

<LiveCode example="empty-state-type-first-time-quick-setup-01" screenshot fullWidth>
  ```tsx lines theme={null}
  import { useState, useEffect } from "react";
  import { Text, Flex, Button } from "@servicetitan/anvil2";
  import emptyStateImageLight from "../assets/empty_state-first_use-light.png";
  import emptyStateImageDark from "../assets/empty_state-first_use-dark.png";

  function App() {
    // Temp until usePrefersColorScheme is fixed
    const [isDark, setIsDark] = useState(
      () => window.matchMedia("(prefers-color-scheme: dark)").matches,
    );

    useEffect(() => {
      const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
      const handler = (e: MediaQueryListEvent) => setIsDark(e.matches);
      mediaQuery.addEventListener("change", handler);
      return () => mediaQuery.removeEventListener("change", handler);
    }, []);
    // temp end

    const emptyStateImage = isDark ? emptyStateImageDark : emptyStateImageLight;
    return (
      <Flex
        alignItems="center"
        justifyContent="center"
        direction="column"
        gap={6}
        style={{ maxWidth: "420px" }}
      >
        <Flex alignItems="center" direction="column">
          <img
            src={emptyStateImage}
            alt="First-time setup illustration"
            height={200}
          />

          <Text variant="headline" el="h2">
            Set Up Your Report
          </Text>
          <Text style={{ textAlign: "center" }}>
            You can now create reports with rules based on your business needs.
            Then assign employees to each report. No more complex spreadsheets
            just to keep track of your business health.
          </Text>
        </Flex>
        <Button appearance="primary">Get Started</Button>
      </Flex>
    );
  }

  export default App;
  ```
</LiveCode>

<LiveCode example="empty-state-type-first-time-quick-setup-02" screenshot fullWidth>
  ```tsx lines theme={null}
  import { useState, useEffect } from "react";
  import { Text, Flex, Button, Card } from "@servicetitan/anvil2";
  import emptyStateImageLight from "../assets/empty_state-first_use-light.png";
  import emptyStateImageDark from "../assets/empty_state-first_use-dark.png";

  function App() {
    // Temp until usePrefersColorScheme is fixed
    const [isDark, setIsDark] = useState(
      () => window.matchMedia("(prefers-color-scheme: dark)").matches,
    );

    useEffect(() => {
      const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
      const handler = (e: MediaQueryListEvent) => setIsDark(e.matches);
      mediaQuery.addEventListener("change", handler);
      return () => mediaQuery.removeEventListener("change", handler);
    }, []);
    // temp end

    const emptyStateImage = isDark ? emptyStateImageDark : emptyStateImageLight;
    return (
      <Flex
        alignItems="center"
        justifyContent="center"
        direction="column"
        gap={6}
        style={{ maxWidth: "420px" }}
      >
        <Flex alignItems="center" direction="column">
          <img
            src={emptyStateImage}
            alt="First-time setup illustration"
            height={200}
          />

          <Text variant="headline" el="h2">
            Set Up Your Report
          </Text>
          <Text>
            Say goodbye to complex spreadsheets. You can now create and assign
            reports with custom rules.
          </Text>
        </Flex>
        <Card flexDirection="column" style={{ maxWidth: "350px" }}>
          <Text style={{ textAlign: "center" }}>
            <b>Before starting, grab these for reference:</b>
          </Text>
          <Text>- Employee Information</Text>
          <Text>- Company Information</Text>
        </Card>
        <Button appearance="primary">Get Started</Button>
      </Flex>
    );
  }

  export default App;
  ```
</LiveCode>

## Recurring Empty States

After setup has been completed, some screens/sections display empty states as a natural state of the screen/section or as an error message. Since these empty states can happen frequently, their main goal is to communicate information, and don’t need to be celebratory or decorative. Recurring empty states may or may not have a CTA, depending on whether the empty state is within the user’s control.

### Communicate Functional Feedback

The purpose of some recurring empty states is to show functional feedback about the information on that screen. The functional feedback is usually neutral, but it can be positive or negative in some situations.

#### Neutral Functional Feedback

The most common type of recurring empty state feedback. If whether or not a user populates a screen/section is up to preference and/or doesn’t inhibit any workflows, neutral functional feedback is appropriate. In some instances, a positive empty state that occurs frequently may be displayed as “neutral” in order to tone it down visually.

Recommendations:

* Keep feedback concise and direct.
* If a CTA is applicable, use an in-line link.
* Use grayscale / subdued text.
* A grayscale icon may be used to increase understanding but is not required.
* Do not use illustrations.

<LiveCode example="empty-state-recurring-01" screenshot fullWidth>
  ```tsx lines theme={null}
  import { Text, Flex } from "@servicetitan/anvil2";

  function App() {
    return (
      <Flex alignItems="center" direction="column" style={{ maxWidth: "420px" }}>
        <Text subdued size="small" style={{ textAlign: "center" }}>
          You have no billing history. When an invoice is complete, it will appear
          here.
        </Text>
      </Flex>
    );
  }

  export default App;
  ```
</LiveCode>

<LiveCode example="empty-state-recurring-02" screenshot fullWidth>
  ```tsx lines theme={null}
  import { Text, Flex, Icon } from "@servicetitan/anvil2";
  import MoneyOff from "@servicetitan/anvil2/assets/icons/material/round/money_off.svg";
  import { core } from "@servicetitan/anvil2/token";

  function App() {
    return (
      <Flex
        alignItems="center"
        direction="column"
        gap={2}
        style={{ maxWidth: "420px" }}
      >
        <Icon
          svg={MoneyOff}
          size="xlarge"
          color={core.semantic.ForegroundColorSubdued.value}
        />
        <Text subdued size="small" style={{ textAlign: "center" }}>
          You have no billing history. When an invoice is complete, it will appear
          here. View active invoices.
        </Text>
      </Flex>
    );
  }

  export default App;
  ```
</LiveCode>

#### Negative Functional Feedback

Functional feedback is negative when workflows are inhibited by the empty state. The user may not be able to immediately move forward until the empty state is resolved, or the empty state could have potential negative impact on future workflows or processes. In these instances, the user should always have an explanation on what to do next, or an in-line CTA to help them resolve the empty state as quickly as possible.

* Keep feedback concise and direct.
* Must provide instructions or use a CTA that helps the user resolve the empty state.
* Use grayscale / subdued text.
* Must use a red icon that denotes warning or caution.
* Do not use illustrations.

<LiveCode example="empty-state-recurring-03" screenshot fullWidth>
  ```tsx lines theme={null}
  import { Text, Flex, Icon } from "@servicetitan/anvil2";
  import NotInterested from "@servicetitan/anvil2/assets/icons/material/round/not_interested.svg";
  import { core } from "@servicetitan/anvil2/token";

  function App() {
    return (
      <Flex
        alignItems="center"
        direction="column"
        gap={2}
        style={{ maxWidth: "420px" }}
      >
        <Icon
          svg={NotInterested}
          size="xlarge"
          color={core.semantic.ForegroundColorDanger.value}
        />
        <Text subdued size="small" style={{ textAlign: "center" }}>
          No results found. Try another search or create a new report by clicking
          the button above.
        </Text>
      </Flex>
    );
  }

  export default App;
  ```
</LiveCode>

<LiveCode example="empty-state-recurring-04" screenshot fullWidth>
  ```tsx lines theme={null}
  import { Text, Flex, Icon, Link } from "@servicetitan/anvil2";
  import NotInterested from "@servicetitan/anvil2/assets/icons/material/round/not_interested.svg";
  import { core } from "@servicetitan/anvil2/token";

  function App() {
    return (
      <Flex
        alignItems="center"
        direction="column"
        gap={2}
        style={{ maxWidth: "420px" }}
      >
        <Icon
          svg={NotInterested}
          size="xlarge"
          color={core.semantic.ForegroundColorDanger.value}
        />
        <Text subdued size="small" style={{ textAlign: "center" }}>
          No results found. Try another search or{" "}
          <Link href="#">create a new report</Link>.
        </Text>
      </Flex>
    );
  }

  export default App;
  ```
</LiveCode>

#### Positive Functional Feedback

If the empty state is the desired state for the screen/section, the functional feedback should be positive. Since the feedback is positive, it could be a celebratory moment and an illustration may be appropriate. On the other hand, some positive empty states that occurs frequently may be displayed as “neutral” in order to tone them down visually.

* Keep feedback concise with an encouraging tone.
* If a CTA is applicable, use an in-line link.
* Use grayscale / subdued text.
* A full color icon may be used to increase understanding but is not required.
* An illustration may be used.
* Can follow the appearance recommendations for Neutral Functional Feedback.

<LiveCode example="empty-state-recurring-05" screenshot fullWidth>
  ```tsx lines theme={null}
  import { Text, Flex, Icon } from "@servicetitan/anvil2";
  import CheckCircle from "@servicetitan/anvil2/assets/icons/material/round/check_circle_outline.svg";
  import { core } from "@servicetitan/anvil2/token";

  function App() {
    return (
      <Flex
        alignItems="center"
        direction="column"
        gap={2}
        style={{ maxWidth: "420px" }}
      >
        <Icon
          svg={CheckCircle}
          size="xlarge"
          color={core.semantic.ForegroundColorPrimary.value}
        />
        <Text subdued size="small" style={{ textAlign: "center" }}>
          Your report is up-to-date! Check back later for new updates.
        </Text>
      </Flex>
    );
  }

  export default App;
  ```
</LiveCode>

<LiveCode example="empty-state-recurring-06" screenshot fullWidth>
  ```tsx lines theme={null}
  import { useState, useEffect } from "react";
  import { Text, Flex, Link } from "@servicetitan/anvil2";
  import emptyStateImageLight from "../assets/empty_state-success-light.png";
  import emptyStateImageDark from "../assets/empty_state-success-dark.png";

  function App() {
    // Temp until usePrefersColorScheme is fixed
    const [isDark, setIsDark] = useState(
      () => window.matchMedia("(prefers-color-scheme: dark)").matches,
    );

    useEffect(() => {
      const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
      const handler = (e: MediaQueryListEvent) => setIsDark(e.matches);
      mediaQuery.addEventListener("change", handler);
      return () => mediaQuery.removeEventListener("change", handler);
    }, []);
    // temp end

    const emptyStateImage = isDark ? emptyStateImageDark : emptyStateImageLight;
    return (
      <Flex
        alignItems="center"
        justifyContent="center"
        direction="column"
        gap={6}
        style={{ maxWidth: "420px" }}
      >
        <Flex alignItems="center" direction="column">
          <img src={emptyStateImage} alt="Success illustration" height={200} />

          <Text subdued size="small" style={{ textAlign: "center" }}>
            Your report is up-to-date! Check back later for new updates. To see
            the history of changes, <Link href="#">view your sync log</Link>.
          </Text>
        </Flex>
      </Flex>
    );
  }

  export default App;
  ```
</LiveCode>

## Empty States Outside of User's Control

Some empty states are caused by something outside of the user’s control. Something may be processing by ServiceTitan or a third-party vendor, or next steps may be dependent on a customer or another employee.

* Be transparent about what the empty state is, who is responsible for resolving it, and when that resolution may occur.
* Keep tone friendly and encouraging.
* Follow appearance recommendations accordingly for a first time setup or a recurring empty state (positive or neutral feedback).
* A full color or grayscale icon may be used to increase understanding but is not required.
* An illustration may be used.

<LiveCode example="empty-state-out-of-user-control-01" screenshot fullWidth>
  ```tsx lines theme={null}
  import { useState, useEffect } from "react";
  import { Text, Flex } from "@servicetitan/anvil2";
  import emptyStateImageLight from "../assets/empty_state-user_cleared-light.png";
  import emptyStateImageDark from "../assets/empty_state-user_cleared-dark.png";

  function App() {
    // Temp until usePrefersColorScheme is fixed
    const [isDark, setIsDark] = useState(
      () => window.matchMedia("(prefers-color-scheme: dark)").matches,
    );

    useEffect(() => {
      const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
      const handler = (e: MediaQueryListEvent) => setIsDark(e.matches);
      mediaQuery.addEventListener("change", handler);
      return () => mediaQuery.removeEventListener("change", handler);
    }, []);
    // temp end

    const emptyStateImage = isDark ? emptyStateImageDark : emptyStateImageLight;
    return (
      <Flex
        alignItems="center"
        justifyContent="center"
        direction="column"
        gap={6}
        style={{ maxWidth: "420px" }}
      >
        <Flex alignItems="center" direction="column">
          <img
            src={emptyStateImage}
            alt="Background processing illustration"
            height={200}
          />

          <Text variant="headline" el="h2">
            You Account is Being Migrated
          </Text>
          <Text subdued style={{ textAlign: "center" }}>
            We are working on setting up your account. Verifying the information
            can take up to 3 business days. You will receive an email when your
            account is ready.
          </Text>
        </Flex>
      </Flex>
    );
  }

  export default App;
  ```
</LiveCode>

<LiveCode example="empty-state-out-of-user-control-02" screenshot fullWidth>
  ```tsx lines theme={null}
  import { Text, Flex, Icon } from "@servicetitan/anvil2";
  import Build from "@servicetitan/anvil2/assets/icons/material/round/build.svg";
  import { core } from "@servicetitan/anvil2/token";

  function App() {
    return (
      <Flex
        alignItems="center"
        direction="column"
        gap={2}
        style={{ maxWidth: "420px" }}
      >
        <Icon
          svg={Build}
          size="xlarge"
          color={core.semantic.ForegroundColorSubdued.value}
        />
        <Text subdued style={{ textAlign: "center" }}>
          This account is going through routine maintenance. Your connection
          should resume as normal in a couple hours.
        </Text>
      </Flex>
    );
  }

  export default App;
  ```
</LiveCode>

## Error Empty States

Error empty states may occur due to a server issue (like a 404 or 500 error), or the user’s internet access could be interrupted by a poor connection.

* Be transparent about what the empty state is, who is responsible for resolving it, and when that resolution may occur.
* Must provide instructions or use a CTA that helps the user resolve the empty state.
* Follow appearance recommendations accordingly for a first time setup or a recurring empty state (positive or neutral feedback).
* A full color or grayscale icon may be used to increase understanding but is not required.
* An illustration may be used.

<LiveCode example="empty-state-error-01" screenshot fullWidth>
  ```tsx lines theme={null}
  import { useState, useEffect } from "react";
  import { Text, Flex, Link, Button } from "@servicetitan/anvil2";
  import emptyStateImageLight from "../assets/empty_state-error-light.png";
  import emptyStateImageDark from "../assets/empty_state-error-dark.png";

  function App() {
    // Temp until usePrefersColorScheme is fixed
    const [isDark, setIsDark] = useState(
      () => window.matchMedia("(prefers-color-scheme: dark)").matches,
    );

    useEffect(() => {
      const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
      const handler = (e: MediaQueryListEvent) => setIsDark(e.matches);
      mediaQuery.addEventListener("change", handler);
      return () => mediaQuery.removeEventListener("change", handler);
    }, []);
    // temp end

    const emptyStateImage = isDark ? emptyStateImageDark : emptyStateImageLight;
    return (
      <Flex
        alignItems="center"
        justifyContent="center"
        direction="column"
        gap={6}
        style={{ maxWidth: "420px" }}
      >
        <Flex alignItems="center" direction="column">
          <img src={emptyStateImage} alt="Error illustration" height={200} />

          <Text variant="headline" el="h2">
            We Didn&apos;t Catch That
          </Text>
          <Text subdued style={{ textAlign: "center" }}>
            The server didn&apos;t recognize your request. We are currently
            investigating the problem. Go back or <Link href="#">try again</Link>.
          </Text>
        </Flex>
        <Button>Go Back</Button>
      </Flex>
    );
  }

  export default App;
  ```
</LiveCode>

<LiveCode example="empty-state-error-02" screenshot fullWidth>
  ```tsx lines theme={null}
  import { Text, Flex, Icon, Link } from "@servicetitan/anvil2";
  import Error from "@servicetitan/anvil2/assets/icons/material/round/error.svg";
  import { core } from "@servicetitan/anvil2/token";

  function App() {
    return (
      <Flex
        alignItems="center"
        direction="column"
        gap={2}
        style={{ maxWidth: "420px" }}
      >
        <Icon
          svg={Error}
          size="xlarge"
          color={core.semantic.ForegroundColorSubdued.value}
        />
        <Text subdued style={{ textAlign: "center" }}>
          The server didn&apos;t recognize your request. We are currently
          investigating the problem. Go back or <Link href="#">try again</Link>.
        </Text>
      </Flex>
    );
  }

  export default App;
  ```
</LiveCode>

# Additional Content

## Starter Content

To help users new to an app or section, screens which would otherwise be empty can be populated with starter content. Starter content allows users to begin using an app right away, making it easier for them to learn about what an app has to offer.

* Starter content is best for features that store content (such as Pricebook), or create templated content (such as Marketing Pro).
* Use content that has broad appeal and demonstrates primary features.
* Give users the ability to delete and replace starter content.
* If possible, provide content that's personalized.

<LiveCode example="empty-state-starter-content" screenshot fullWidth>
  ```tsx lines theme={null}
  import {
    Text,
    Flex,
    Grid,
    Card,
    ButtonCompound,
    Icon,
  } from "@servicetitan/anvil2";
  import Build from "@servicetitan/anvil2/assets/icons/material/round/build.svg";
  import Book from "@servicetitan/anvil2/assets/icons/material/round/book.svg";
  import Pause from "@servicetitan/anvil2/assets/icons/material/round/pause.svg";
  import Alarm from "@servicetitan/anvil2/assets/icons/material/round/alarm.svg";
  import { core } from "@servicetitan/anvil2/token";

  function App() {
    return (
      <Flex
        alignItems="center"
        justifyContent="center"
        direction="column"
        gap={6}
      >
        <Flex alignItems="center" direction="column">
          <Text variant="headline" el="h2">
            Welcome to Reports
          </Text>
          <Text style={{ textAlign: "center", maxWidth: "420px" }}>
            You don&apos;t have any reports. Here are some suggestions to get
            started with your first one.
          </Text>
        </Flex>
        <Grid
          templateColumns="repeat(2, 1fr)"
          gap={4}
          style={{ maxWidth: "600px" }}
        >
          <Grid.Item>
            <ButtonCompound shape="rounded" style={{ height: "100%" }}>
              <Card gap={4} style={{ height: "100%" }}>
                <Icon
                  svg={Build}
                  color={core.semantic.ForegroundColorSubdued.value}
                  style={{ paddingTop: core.primitive.Size1.value }}
                />
                <Flex direction="column" gap={1}>
                  <Text variant="headline" el="h3" size="small">
                    Unsold Estimates
                  </Text>
                  <Text subdued size="small">
                    Find and target open estimates. Leave no opportunity behind by
                    creating custom parameters with your audience.
                  </Text>
                </Flex>
              </Card>
            </ButtonCompound>
          </Grid.Item>
          <Grid.Item>
            <ButtonCompound shape="rounded" style={{ height: "100%" }}>
              <Card gap={4} style={{ height: "100%" }}>
                <Icon
                  svg={Book}
                  color={core.semantic.ForegroundColorSubdued.value}
                  style={{ paddingTop: core.primitive.Size1.value }}
                />
                <Flex direction="column" gap={1}>
                  <Text variant="headline" el="h3" size="small">
                    Expiring Memberships
                  </Text>
                  <Text subdued size="small">
                    Automatically reach out to customers with soon to expire
                    memberships. Allow customers to see what memberships will
                    expire and when. Prompt them to take action and renew.
                  </Text>
                </Flex>
              </Card>
            </ButtonCompound>
          </Grid.Item>
          <Grid.Item>
            <ButtonCompound shape="rounded" style={{ height: "100%" }}>
              <Card gap={4} style={{ height: "100%" }}>
                <Icon
                  svg={Pause}
                  color={core.semantic.ForegroundColorSubdued.value}
                  style={{ paddingTop: core.primitive.Size1.value }}
                />
                <Flex direction="column" gap={1}>
                  <Text variant="headline" el="h3" size="small">
                    Idle Account
                  </Text>
                  <Text subdued size="small">
                    Follow up with existing customers who have no recent activity
                    in ServiceTitan. Customize the date parameter to find
                    customers who have not called or booked a job.
                  </Text>
                </Flex>
              </Card>
            </ButtonCompound>
          </Grid.Item>
          <Grid.Item>
            <ButtonCompound shape="rounded" style={{ height: "100%" }}>
              <Card gap={4} style={{ height: "100%" }}>
                <Icon
                  svg={Alarm}
                  color={core.semantic.ForegroundColorSubdued.value}
                  style={{ paddingTop: core.primitive.Size1.value }}
                />
                <Flex direction="column" gap={1}>
                  <Text variant="headline" el="h3" size="small">
                    Aging Equipment
                  </Text>
                  <Text subdued size="small">
                    Target customers with specific equipment. Let them know
                    you&apos;re aware of their home and encourage them to take the
                    next step in protecting their home.
                  </Text>
                </Flex>
              </Card>
            </ButtonCompound>
          </Grid.Item>
        </Grid>
      </Flex>
    );
  }

  export default App;
  ```
</LiveCode>

## Educational Content

If the purpose of the screen isn't easily conveyed through an image and tagline, consider showing educational content instead. Educational content helps users understand what a feature will be able to do once it has content.

* Make it possible to dismiss or skip this content.
* Keep it brief.
* Keep content contextual to the screen. This should not be a place to onboard the user to the entire app.

## Best Match

On a search screen, if nothing exactly matches a user's query, content that contains the best match can be displayed by returning results for a query spelled slightly differently. By showing these results, the user may find what they're looking for.

<LiveCode example="empty-state-best-match" screenshot fullWidth>
  ```tsx lines theme={null}
  import { Text, Flex, Card, Icon, Button, Divider } from "@servicetitan/anvil2";
  import emptyStateImageLight from "../assets/empty_state-no_search_results-light.png";
  import emptyStateImageDark from "../assets/empty_state-no_search_results-dark.png";
  import { core } from "@servicetitan/anvil2/token";
  import { useEffect, useState } from "react";
  import Circle from "@servicetitan/anvil2/assets/icons/material/round/circle.svg";

  function App() {
    const [isDark, setIsDark] = useState(
      () => window.matchMedia("(prefers-color-scheme: dark)").matches,
    );

    useEffect(() => {
      const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
      const handler = (e: MediaQueryListEvent) => setIsDark(e.matches);
      mediaQuery.addEventListener("change", handler);
      return () => mediaQuery.removeEventListener("change", handler);
    }, []);
    // temp end

    const emptyStateImage = isDark ? emptyStateImageDark : emptyStateImageLight;
    return (
      <Flex
        alignItems="center"
        justifyContent="center"
        direction="column"
        gap={6}
      >
        <Flex alignItems="center" direction="column">
          <img src={emptyStateImage} alt="No results illustration" height={200} />
          <Text variant="headline" el="h2">
            Sorry, we couldn&apos;t find any results for &quot;
            <span style={{ color: core.semantic.ForegroundColorPrimary.value }}>
              jfjkleiklfj
            </span>
            &quot;
          </Text>
          <Text style={{ textAlign: "center", maxWidth: "420px" }}>
            Remember to check your spelling or try searching for something else.
          </Text>
        </Flex>
        <Flex direction="column" gap={2}>
          <Text variant="headline" el="h3" size="small">
            Try these items instead
          </Text>
          <Card gap={4} alignItems="center" flexDirection="column">
            <Flex direction="row" gap={4} alignItems="center">
              <Flex direction="column" style={{ minWidth: "160px" }}>
                <Text variant="headline" el="h4" size="small">
                  Item 1
                </Text>
                <Text subdued size="small">
                  Item Description
                </Text>
              </Flex>
              <Flex direction="row" gap={2} alignItems="center">
                <Icon
                  svg={Circle}
                  color={core.semantic.StatusColorSuccess.value}
                  size="small"
                />
                <Text size="small">25%</Text>
              </Flex>
              <Button>Select</Button>
            </Flex>
            <Divider />
            <Flex direction="row" gap={4} alignItems="center">
              <Flex direction="column" style={{ minWidth: "160px" }}>
                <Text variant="headline" el="h4" size="small">
                  Item 1
                </Text>
                <Text subdued size="small">
                  Item Description
                </Text>
              </Flex>
              <Flex direction="row" gap={2} alignItems="center">
                <Icon
                  svg={Circle}
                  color={core.semantic.StatusColorSuccess.value}
                  size="small"
                />
                <Text size="small">25%</Text>
              </Flex>
              <Button>Select</Button>
            </Flex>
            <Divider />
            <Flex direction="row" gap={4} alignItems="center">
              <Flex direction="column" style={{ minWidth: "160px" }}>
                <Text variant="headline" el="h4" size="small">
                  Item 1
                </Text>
                <Text subdued size="small">
                  Item Description
                </Text>
              </Flex>
              <Flex direction="row" gap={2} alignItems="center">
                <Icon
                  svg={Circle}
                  color={core.semantic.StatusColorSuccess.value}
                  size="small"
                />
                <Text size="small">25%</Text>
              </Flex>
              <Button>Select</Button>
            </Flex>
          </Card>
        </Flex>
      </Flex>
    );
  }

  export default App;
  ```
</LiveCode>

# Handling Empty States for Tables

Below are some best practice examples of handling empty states for tables.

## First Time

A table with a first time empty state should not appear like a table. The table container can be replaced with a card containing the empty state. Any filters, tabs, and table headers should be hidden. Since the goal of a first time empty state is to show the user how to fill it, we should minimize cognitive load and direct their attention to one place.

A first time empty state for a table may also simply float on the page background instead of being contained in a card.

<LiveCode example="empty-state-datatable-first-use-01" screenshot fullWidth>
  ```tsx lines theme={null}
  import { useState, useEffect } from "react";
  import { Text, Flex, Link, Button, Card } from "@servicetitan/anvil2";
  import emptyStateImageLight from "../assets/empty_state-first_use-light.png";
  import emptyStateImageDark from "../assets/empty_state-first_use-dark.png";

  function App() {
    // Temp until usePrefersColorScheme is fixed
    const [isDark, setIsDark] = useState(
      () => window.matchMedia("(prefers-color-scheme: dark)").matches,
    );

    useEffect(() => {
      const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
      const handler = (e: MediaQueryListEvent) => setIsDark(e.matches);
      mediaQuery.addEventListener("change", handler);
      return () => mediaQuery.removeEventListener("change", handler);
    }, []);
    // temp end

    const emptyStateImage = isDark ? emptyStateImageDark : emptyStateImageLight;
    return (
      <Card
        padding="large"
        gap={6}
        flexDirection="column"
        alignItems="center"
        justifyContent="center"
      >
        <Flex
          alignItems="center"
          direction="column"
          style={{ maxWidth: "420px" }}
        >
          <img
            src={emptyStateImage}
            alt="First-time setup illustration"
            height={200}
          />

          <Text variant="headline" el="h2">
            Create Your First Report
          </Text>
          <Text subdued style={{ textAlign: "center" }}>
            \ Ready to build a great report? If you&apos;re not sure where to
            start, we&apos;ll show you the ropes. <Link href="#">Learn More</Link>
          </Text>
        </Flex>
        <Button>Get Started</Button>
      </Card>
    );
  }

  export default App;
  ```
</LiveCode>

<LiveCode example="empty-state-datatable-first-use-02" screenshot fullWidth>
  ```tsx lines theme={null}
  import { useState, useEffect } from "react";
  import { Text, Flex, Link, Button } from "@servicetitan/anvil2";
  import emptyStateImageLight from "../assets/empty_state-first_use-light.png";
  import emptyStateImageDark from "../assets/empty_state-first_use-dark.png";

  function App() {
    // Temp until usePrefersColorScheme is fixed
    const [isDark, setIsDark] = useState(
      () => window.matchMedia("(prefers-color-scheme: dark)").matches,
    );

    useEffect(() => {
      const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
      const handler = (e: MediaQueryListEvent) => setIsDark(e.matches);
      mediaQuery.addEventListener("change", handler);
      return () => mediaQuery.removeEventListener("change", handler);
    }, []);
    // temp end

    const emptyStateImage = isDark ? emptyStateImageDark : emptyStateImageLight;
    return (
      <Flex
        gap={6}
        direction="column"
        alignItems="center"
        justifyContent="center"
      >
        <Flex
          alignItems="center"
          direction="column"
          style={{ maxWidth: "420px" }}
        >
          <img
            src={emptyStateImage}
            alt="First-time setup illustration"
            height={200}
          />

          <Text variant="headline" el="h2">
            Create Your First Report
          </Text>
          <Text subdued style={{ textAlign: "center" }}>
            \ Ready to build a great report? If you&apos;re not sure where to
            start, we&apos;ll show you the ropes. <Link href="#">Learn More</Link>
          </Text>
        </Flex>
        <Button>Get Started</Button>
      </Flex>
    );
  }

  export default App;
  ```
</LiveCode>

## Recurring

A table with a recurring empty state should show the table container, column header row with header names, and any filters. The row dividers should be removed and the content of the table should include only the empty state.

<LiveCode example="empty-state-datatable-recurring" screenshot fullWidth>
  ```tsx lines theme={null}
  import { Text, Flex, Link } from "@servicetitan/anvil2";
  import { DataTable, createColumnHelper } from "@servicetitan/anvil2/beta";
  import NoSearchResultsLight from "@servicetitan/anvil2-illustrations/illustrations/empty-state-no-search-results-light.svg";
  import NoSearchResultsDark from "@servicetitan/anvil2-illustrations/illustrations/empty-state-no-search-results-dark.svg";

  type Invoice = {
    invoiceNumber: string;
    date: string;
    description: string;
    total: number;
    paid: number;
  };

  const createColumn = createColumnHelper<Invoice>();

  const columns = [
    createColumn("invoiceNumber", {
      headerLabel: "Invoice #",
    }),
    createColumn("date", {
      headerLabel: "Date",
    }),
    createColumn("description", {
      headerLabel: "Description",
    }),
    createColumn("total", {
      headerLabel: "Total",
    }),
    createColumn("paid", {
      headerLabel: "Paid",
    }),
  ];

  function App() {
    return (
      <DataTable
        data={[]}
        columns={columns}
        emptyState={{
          svg: { light: NoSearchResultsLight, dark: NoSearchResultsDark },
          content: (
            <Flex
              alignItems="center"
              direction="column"
              gap={2}
              style={{ maxWidth: "420px" }}
            >
              <Text subdued size="small" style={{ textAlign: "center" }}>
                No results found. Try another search or{" "}
                <Link href="#">create a new report</Link>.
              </Text>
            </Flex>
          ),
        }}
      />
    );
  }

  export default App;
  ```
</LiveCode>
