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

# Decorative Icon

> Reference implementations for decorative icon lockups.

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

Decorative icons pair a rounded, tinted icon with text to add visual weight to a headline, card, or list row — without implying an action. Each example below is a starting point: copy it, then swap the icon and text for your use case.

## Resources

<Card title="Icon" icon="layer-group" href="/docs/web/components/icon/design">
  Icon component anatomy, sizing, and usage guidance.
</Card>

<Card title="Decorative icon" icon="figma" href="https://www.figma.com/design/9OB8FwOZZc079PBR6XY8vP/Anvil-Blocks?node-id=2189-16655">
  Figma reference implementations for decorative icon lockups.
</Card>

## Interactive Card With Headline

Lead an interactive card with a decorative icon and a headline for a single, tappable entry point.

<LiveCode example="block-decorative-icon-interactive-card-headline" screenshot fullWidth>
  ```tsx lines theme={null}
  import { Flex, Icon, Text, type Svg } from "@servicetitan/anvil2";
  import { InteractiveCard } from "@servicetitan/anvil2/beta";
  import Appointments from "@servicetitan/anvil2/assets/icons/st/appointments.svg";

  type DecorativeIconSize = "small" | "medium" | "large" | "xlarge";

  const decorativeIconCircleSize: Record<DecorativeIconSize, string> = {
    small: "var(--a2-size-6, 24px)",
    medium: "var(--a2-size-8, 32px)",
    large: "var(--a2-size-10, 40px)",
    xlarge: "var(--a2-size-12, 48px)",
  };

  function DecorativeIcon({
    svg,
    size = "medium",
  }: {
    svg: Svg;
    size?: DecorativeIconSize;
  }) {
    return (
      <Icon
        svg={svg}
        size={size}
        color="var(--a2-color-blue-700)"
        aria-hidden
        style={{
          alignItems: "center",
          background: "var(--a2-color-blue-100)",
          borderRadius: "50%",
          display: "inline-flex",
          flexShrink: 0,
          height: decorativeIconCircleSize[size],
          justifyContent: "center",
          width: decorativeIconCircleSize[size],
        }}
      />
    );
  }

  function App() {
    return (
      <InteractiveCard
        wrapperProps={{ "aria-label": "Appointments card" }}
        actionProps={{
          "aria-label": "Appointments",
          onClick: () => console.log("Appointments"),
        }}
        contentProps={{
          padding: "medium",
          style: { width: "min(446px, 100%)", boxSizing: "border-box" },
        }}
      >
        <Flex alignItems="center" gap={4}>
          <DecorativeIcon svg={Appointments} size="large" />
          <Text variant="headline" el="h3" size="small">
            Appointments
          </Text>
        </Flex>
      </InteractiveCard>
    );
  }

  export default App;
  ```
</LiveCode>

## In Interactive Cards

Differentiate a set of interactive cards, each with a decorative icon, title, and supporting text.

<LiveCode example="block-decorative-icon-in-interactive-cards" screenshot fullWidth>
  ```tsx lines theme={null}
  import { Flex, Icon, Text, type Svg } from "@servicetitan/anvil2";
  import { InteractiveCard } from "@servicetitan/anvil2/beta";
  import FormatListBulleted from "@servicetitan/anvil2/assets/icons/material/round/format_list_bulleted.svg";
  import CalendarMonth from "@servicetitan/anvil2/assets/icons/material/round/calendar_month.svg";

  type DecorativeIconSize = "small" | "medium" | "large" | "xlarge";

  const decorativeIconCircleSize: Record<DecorativeIconSize, string> = {
    small: "var(--a2-size-6, 24px)",
    medium: "var(--a2-size-8, 32px)",
    large: "var(--a2-size-10, 40px)",
    xlarge: "var(--a2-size-12, 48px)",
  };

  function DecorativeIcon({
    svg,
    size = "medium",
  }: {
    svg: Svg;
    size?: DecorativeIconSize;
  }) {
    return (
      <Icon
        svg={svg}
        size={size}
        color="var(--a2-color-blue-700)"
        aria-hidden
        style={{
          alignItems: "center",
          background: "var(--a2-color-blue-100)",
          borderRadius: "50%",
          display: "inline-flex",
          flexShrink: 0,
          height: decorativeIconCircleSize[size],
          justifyContent: "center",
          width: decorativeIconCircleSize[size],
        }}
      />
    );
  }

  const LOREM =
    "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius.";

  const inCardsExamples: { icon: Svg; title: string; description: string }[] = [
    { icon: FormatListBulleted, title: "Create a list", description: LOREM },
    { icon: CalendarMonth, title: "Create a calendar", description: LOREM },
  ];

  function App() {
    return (
      <Flex
        direction="column"
        alignItems="center"
        gap={4}
        style={{ width: "100%" }}
        md={{
          direction: "row",
          alignItems: "stretch",
          justifyContent: "center",
        }}
      >
        {inCardsExamples.map(({ icon, title, description }) => (
          <InteractiveCard
            key={title}
            wrapperProps={{
              "aria-label": `${title} card`,
              style: { width: "100%", maxWidth: 320 },
            }}
            actionProps={{
              "aria-label": title,
              onClick: () => console.log(title),
            }}
            contentProps={{
              padding: "medium",
              flexDirection: "column",
              gap: 4,
              style: { width: "100%" },
            }}
          >
            <DecorativeIcon svg={icon} size="large" />
            <Flex direction="column" gap={1}>
              <Text variant="headline" el="h3" size="small">
                {title}
              </Text>
              <Text variant="body" size="small" subdued>
                {description}
              </Text>
            </Flex>
          </InteractiveCard>
        ))}
      </Flex>
    );
  }

  export default App;
  ```
</LiveCode>

## Settings Page

Anchor each row of a settings or configuration list with a decorative icon, description, and action.

<LiveCode example="block-decorative-icon-settings-page" screenshot fullWidth>
  ```tsx lines theme={null}
  import { Fragment } from "react";
  import {
    Button,
    Card,
    Divider,
    Flex,
    Icon,
    Text,
    type Svg,
  } from "@servicetitan/anvil2";
  import AssignmentInd from "@servicetitan/anvil2/assets/icons/material/round/assignment_ind.svg";
  import SyncAlt from "@servicetitan/anvil2/assets/icons/material/round/sync_alt.svg";
  import Rule from "@servicetitan/anvil2/assets/icons/material/round/rule.svg";
  import Cases from "@servicetitan/anvil2/assets/icons/material/round/cases.svg";
  import ContactEmergency from "@servicetitan/anvil2/assets/icons/material/round/contact_emergency.svg";

  type DecorativeIconSize = "small" | "medium" | "large" | "xlarge";

  const decorativeIconCircleSize: Record<DecorativeIconSize, string> = {
    small: "var(--a2-size-6, 24px)",
    medium: "var(--a2-size-8, 32px)",
    large: "var(--a2-size-10, 40px)",
    xlarge: "var(--a2-size-12, 48px)",
  };

  function DecorativeIcon({
    svg,
    size = "medium",
  }: {
    svg: Svg;
    size?: DecorativeIconSize;
  }) {
    return (
      <Icon
        svg={svg}
        size={size}
        color="var(--a2-color-blue-700)"
        aria-hidden
        style={{
          alignItems: "center",
          background: "var(--a2-color-blue-100)",
          borderRadius: "50%",
          display: "inline-flex",
          flexShrink: 0,
          height: decorativeIconCircleSize[size],
          justifyContent: "center",
          width: decorativeIconCircleSize[size],
        }}
      />
    );
  }

  const settingsPageSections: {
    icon: Svg;
    title: string;
    description: string;
  }[] = [
    {
      icon: AssignmentInd,
      title: "Persona",
      description:
        "Define your Voice Agent's brand identity and foundational knowledge to ensure a consistent and informed customer experience",
    },
    {
      icon: SyncAlt,
      title: "Conversation Flow",
      description:
        "Configure the interaction logic and specialized responses that guide your Voice Agent through various customer scenarios",
    },
    {
      icon: Rule,
      title: "Rules",
      description:
        "Define permissions for Voice Agent actions, such as booking jobs and processing payments",
    },
    {
      icon: Cases,
      title: "Job Types and Fees",
      description:
        "Manage job types, dispatch fees, and how fees are communicated to your customers",
    },
    {
      icon: ContactEmergency,
      title: "Escalations and Fallbacks",
      description:
        "Manage the transition from Voice Agent to human support to ensure every call reaches a resolution",
    },
  ];

  function App() {
    return (
      <Card
        padding="0"
        flexDirection="column"
        style={{ width: "min(800px, 100%)", boxSizing: "border-box" }}
      >
        {settingsPageSections.map((section, index) => (
          <Fragment key={section.title}>
            {index > 0 && <Divider />}
            <div style={{ padding: 24 }}>
              <Flex
                justifyContent="space-between"
                alignItems="start"
                gap={4}
                md={{ alignItems: "center" }}
              >
                <Flex
                  direction="column"
                  alignItems="start"
                  gap={4}
                  style={{ flex: 1, minWidth: 0 }}
                  md={{ direction: "row" }}
                >
                  <DecorativeIcon svg={section.icon} size="large" />
                  <Flex
                    direction="column"
                    gap={1}
                    style={{ flex: 1, minWidth: 0 }}
                  >
                    <Text inline style={{ fontWeight: "bold" }}>
                      {section.title}
                    </Text>
                    <Text subdued>{section.description}</Text>
                  </Flex>
                </Flex>
                <Button
                  appearance="secondary"
                  onClick={() => console.log(`Edit ${section.title}`)}
                  style={{ flexShrink: 0 }}
                >
                  Edit
                </Button>
              </Flex>
            </div>
          </Fragment>
        ))}
      </Card>
    );
  }

  export default App;
  ```
</LiveCode>

## Headline Lockups

Pair a decorative icon with a headline across sizes, from large headings down to labels.

<LiveCode example="block-decorative-icon-headline-lockups" screenshot fullWidth>
  ```tsx lines theme={null}
  import { Flex, Icon, Text, type Svg } from "@servicetitan/anvil2";
  import Face2 from "@servicetitan/anvil2/assets/icons/material/round/face_2.svg";

  type DecorativeIconSize = "small" | "medium" | "large" | "xlarge";

  const decorativeIconCircleSize: Record<DecorativeIconSize, string> = {
    small: "var(--a2-size-6, 24px)",
    medium: "var(--a2-size-8, 32px)",
    large: "var(--a2-size-10, 40px)",
    xlarge: "var(--a2-size-12, 48px)",
  };

  function DecorativeIcon({
    svg,
    size = "medium",
  }: {
    svg: Svg;
    size?: DecorativeIconSize;
  }) {
    return (
      <Icon
        svg={svg}
        size={size}
        color="var(--a2-color-blue-700)"
        aria-hidden
        style={{
          alignItems: "center",
          background: "var(--a2-color-blue-100)",
          borderRadius: "50%",
          display: "inline-flex",
          flexShrink: 0,
          height: decorativeIconCircleSize[size],
          justifyContent: "center",
          width: decorativeIconCircleSize[size],
        }}
      />
    );
  }

  type HeadlineLockup =
    | {
        decorativeSize: DecorativeIconSize;
        variant: "headline";
        el: "h1" | "h2" | "h3" | "h4";
        size: DecorativeIconSize;
        label: string;
      }
    | {
        decorativeSize: DecorativeIconSize;
        variant: "eyebrow";
        size: "medium";
        label: string;
      };

  const headlineLockups: HeadlineLockup[] = [
    {
      decorativeSize: "xlarge",
      variant: "headline",
      el: "h1",
      size: "xlarge",
      label: "This is a headline!",
    },
    {
      decorativeSize: "large",
      variant: "headline",
      el: "h2",
      size: "large",
      label: "This is a headline!",
    },
    {
      decorativeSize: "medium",
      variant: "headline",
      el: "h3",
      size: "medium",
      label: "This is a headline!",
    },
    {
      decorativeSize: "small",
      variant: "headline",
      el: "h4",
      size: "small",
      label: "This is a headline!",
    },
    {
      decorativeSize: "small",
      variant: "eyebrow",
      size: "medium",
      label: "This is a label!",
    },
  ];

  function App() {
    return (
      <Flex direction="column" gap={6} alignItems="start">
        {headlineLockups.map((lockup) => (
          <Flex
            key={`${lockup.variant}-${lockup.decorativeSize}-${lockup.label}`}
            alignItems="center"
            gap={4}
          >
            <DecorativeIcon svg={Face2} size={lockup.decorativeSize} />
            {lockup.variant === "headline" ? (
              <Text variant="headline" el={lockup.el} size={lockup.size}>
                {lockup.label}
              </Text>
            ) : (
              <Text variant="eyebrow" size={lockup.size}>
                {lockup.label}
              </Text>
            )}
          </Flex>
        ))}
      </Flex>
    );
  }

  export default App;
  ```
</LiveCode>
