# SparklineInteractiveHeader

**📖 Live documentation:** https://cds.coinbase.com/components/charts/SparklineInteractiveHeader/

The SparklineInteractiveHeader is used to display chart information that changes over time

## Import

```tsx
import { SparklineInteractiveHeader } from '@coinbase/cds-web/visualizations/sparkline'
```

## Examples

:::tip Accessibility tip
When possible combining content that is contextually related benefits screen reader users. The interactive header within Sparkline is one of these moments. Use an accessibilityLabel prop or aria-label to set the entire context of the interactive header. This way screen reader users will hear the asset name, price, and direction all in one sentence.
:::

### Default usage

```jsx live
() => {
  const periods = [
    { label: '1H', value: 'hour' },
    { label: '1D', value: 'day' },
    { label: '1W', value: 'week' },
    { label: '1M', value: 'month' },
    { label: '1Y', value: 'year' },
    { label: 'All', value: 'all' },
  ];

  const formatDate = useCallback((value, period) => {
    if (period === 'hour' || period === 'day')
      return value.toLocaleTimeString('en-US', { hour: 'numeric', minute: 'numeric' });
    if (period === 'week' || period === 'month')
      return value.toLocaleDateString('en-US', { month: 'numeric', day: 'numeric' });
    return value.toLocaleDateString('en-US', { month: 'numeric', year: 'numeric' });
  }, []);

  const formatPrice = (num) => num.toLocaleString('en-US', { maximumFractionDigits: 2 });

  const generateSubHead = useCallback((point, period) => {
    const firstPoint = sparklineInteractiveData[period][0];
    const increase = point.value > firstPoint.value;
    return {
      percent: `${formatPrice(Math.abs((point.value - firstPoint.value) / firstPoint.value) * 100)}%`,
      sign: increase ? 'upwardTrend' : 'downwardTrend',
      variant: increase ? 'positive' : 'negative',
      priceChange: `$${formatPrice(Math.abs(point.value - firstPoint.value))}`,
    };
  }, []);

  const headerRef = useRef(null);
  const [currentPeriod, setCurrentPeriod] = useState('day');
  const data = sparklineInteractiveData[currentPeriod];
  const lastPoint = data[data.length - 1];

  const handleScrub = useCallback(
    ({ point, period }) => {
      headerRef.current?.update({
        title: `$${point.value.toLocaleString('en-US')}`,
        subHead: generateSubHead(point, period),
      });
    },
    [generateSubHead],
  );

  const handleScrubEnd = useCallback(() => {
    headerRef.current?.update({
      title: `$${formatPrice(lastPoint.value)}`,
      subHead: generateSubHead(lastPoint, currentPeriod),
    });
  }, [lastPoint, currentPeriod, generateSubHead]);

  const handlePeriodChanged = useCallback(
    (period) => {
      setCurrentPeriod(period);
      const newData = sparklineInteractiveData[period];
      const newLastPoint = newData[newData.length - 1];
      headerRef.current?.update({
        title: `$${formatPrice(newLastPoint.value)}`,
        subHead: generateSubHead(newLastPoint, period),
      });
    },
    [generateSubHead],
  );

  return (
    <Box padding={3} width="100%">
      <SparklineInteractive
        data={sparklineInteractiveData}
        defaultPeriod="day"
        formatDate={formatDate}
        headerNode={
          <SparklineInteractiveHeader
            ref={headerRef}
            defaultLabel="Bitcoin Price"
            defaultSubHead={generateSubHead(lastPoint, currentPeriod)}
            defaultTitle={`$${formatPrice(lastPoint.value)}`}
          />
        }
        onPeriodChanged={handlePeriodChanged}
        onScrub={handleScrub}
        onScrubEnd={handleScrubEnd}
        periods={periods}
        strokeColor="#F7931A"
      />
    </Box>
  );
};
```

### Fill

The fill will be added by default

```jsx live
() => {
  const periods = [
    { label: '1H', value: 'hour' },
    { label: '1D', value: 'day' },
    { label: '1W', value: 'week' },
    { label: '1M', value: 'month' },
    { label: '1Y', value: 'year' },
    { label: 'All', value: 'all' },
  ];

  const formatDate = useCallback((value, period) => {
    if (period === 'hour' || period === 'day')
      return value.toLocaleTimeString('en-US', { hour: 'numeric', minute: 'numeric' });
    if (period === 'week' || period === 'month')
      return value.toLocaleDateString('en-US', { month: 'numeric', day: 'numeric' });
    return value.toLocaleDateString('en-US', { month: 'numeric', year: 'numeric' });
  }, []);

  const formatPrice = (num) => num.toLocaleString('en-US', { maximumFractionDigits: 2 });

  const generateSubHead = useCallback((point, period) => {
    const firstPoint = sparklineInteractiveData[period][0];
    const increase = point.value > firstPoint.value;
    return {
      percent: `${formatPrice(Math.abs((point.value - firstPoint.value) / firstPoint.value) * 100)}%`,
      sign: increase ? 'upwardTrend' : 'downwardTrend',
      variant: increase ? 'positive' : 'negative',
      priceChange: `$${formatPrice(Math.abs(point.value - firstPoint.value))}`,
    };
  }, []);

  const headerRef = useRef(null);
  const [currentPeriod, setCurrentPeriod] = useState('day');
  const data = sparklineInteractiveData[currentPeriod];
  const lastPoint = data[data.length - 1];

  const handleScrub = useCallback(
    ({ point, period }) => {
      headerRef.current?.update({
        title: `$${point.value.toLocaleString('en-US')}`,
        subHead: generateSubHead(point, period),
      });
    },
    [generateSubHead],
  );

  const handleScrubEnd = useCallback(() => {
    headerRef.current?.update({
      title: `$${formatPrice(lastPoint.value)}`,
      subHead: generateSubHead(lastPoint, currentPeriod),
    });
  }, [lastPoint, currentPeriod, generateSubHead]);

  const handlePeriodChanged = useCallback(
    (period) => {
      setCurrentPeriod(period);
      const newData = sparklineInteractiveData[period];
      const newLastPoint = newData[newData.length - 1];
      headerRef.current?.update({
        title: `$${formatPrice(newLastPoint.value)}`,
        subHead: generateSubHead(newLastPoint, period),
      });
    },
    [generateSubHead],
  );

  return (
    <Box padding={3} width="100%">
      <SparklineInteractive
        fill
        data={sparklineInteractiveData}
        defaultPeriod="day"
        formatDate={formatDate}
        headerNode={
          <SparklineInteractiveHeader
            ref={headerRef}
            defaultLabel="Bitcoin Price"
            defaultSubHead={generateSubHead(lastPoint, currentPeriod)}
            defaultTitle={`$${formatPrice(lastPoint.value)}`}
          />
        }
        onPeriodChanged={handlePeriodChanged}
        onScrub={handleScrub}
        onScrubEnd={handleScrubEnd}
        periods={periods}
        strokeColor="#F7931A"
      />
    </Box>
  );
};
```

### Compact

```jsx live
() => {
  const periods = [
    { label: '1H', value: 'hour' },
    { label: '1D', value: 'day' },
    { label: '1W', value: 'week' },
    { label: '1M', value: 'month' },
    { label: '1Y', value: 'year' },
    { label: 'All', value: 'all' },
  ];

  const formatDate = useCallback((value, period) => {
    if (period === 'hour' || period === 'day')
      return value.toLocaleTimeString('en-US', { hour: 'numeric', minute: 'numeric' });
    if (period === 'week' || period === 'month')
      return value.toLocaleDateString('en-US', { month: 'numeric', day: 'numeric' });
    return value.toLocaleDateString('en-US', { month: 'numeric', year: 'numeric' });
  }, []);

  const formatPrice = (num) => num.toLocaleString('en-US', { maximumFractionDigits: 2 });

  const generateSubHead = useCallback((point, period) => {
    const firstPoint = sparklineInteractiveData[period][0];
    const increase = point.value > firstPoint.value;
    return {
      percent: `${formatPrice(Math.abs((point.value - firstPoint.value) / firstPoint.value) * 100)}%`,
      sign: increase ? 'upwardTrend' : 'downwardTrend',
      variant: increase ? 'positive' : 'negative',
      priceChange: `$${formatPrice(Math.abs(point.value - firstPoint.value))}`,
    };
  }, []);

  const headerRef = useRef(null);
  const [currentPeriod, setCurrentPeriod] = useState('day');
  const data = sparklineInteractiveData[currentPeriod];
  const lastPoint = data[data.length - 1];

  const handleScrub = useCallback(
    ({ point, period }) => {
      headerRef.current?.update({
        title: `$${point.value.toLocaleString('en-US')}`,
        subHead: generateSubHead(point, period),
      });
    },
    [generateSubHead],
  );

  const handleScrubEnd = useCallback(() => {
    headerRef.current?.update({
      title: `$${formatPrice(lastPoint.value)}`,
      subHead: generateSubHead(lastPoint, currentPeriod),
    });
  }, [lastPoint, currentPeriod, generateSubHead]);

  const handlePeriodChanged = useCallback(
    (period) => {
      setCurrentPeriod(period);
      const newData = sparklineInteractiveData[period];
      const newLastPoint = newData[newData.length - 1];
      headerRef.current?.update({
        title: `$${formatPrice(newLastPoint.value)}`,
        subHead: generateSubHead(newLastPoint, period),
      });
    },
    [generateSubHead],
  );

  return (
    <Box padding={3} width="100%">
      <SparklineInteractive
        compact
        fill
        data={sparklineInteractiveData}
        defaultPeriod="day"
        formatDate={formatDate}
        headerNode={
          <SparklineInteractiveHeader
            ref={headerRef}
            compact
            defaultLabel="Bitcoin Price"
            defaultSubHead={generateSubHead(lastPoint, currentPeriod)}
            defaultTitle={`$${formatPrice(lastPoint.value)}`}
          />
        }
        onPeriodChanged={handlePeriodChanged}
        onScrub={handleScrub}
        onScrubEnd={handleScrubEnd}
        periods={periods}
        strokeColor="#F7931A"
      />
    </Box>
  );
};
```

### Custom Label

```jsx live
() => {
  const periods = [
    { label: '1H', value: 'hour' },
    { label: '1D', value: 'day' },
    { label: '1W', value: 'week' },
    { label: '1M', value: 'month' },
    { label: '1Y', value: 'year' },
    { label: 'All', value: 'all' },
  ];

  const formatDate = useCallback((value, period) => {
    if (period === 'hour' || period === 'day')
      return value.toLocaleTimeString('en-US', { hour: 'numeric', minute: 'numeric' });
    if (period === 'week' || period === 'month')
      return value.toLocaleDateString('en-US', { month: 'numeric', day: 'numeric' });
    return value.toLocaleDateString('en-US', { month: 'numeric', year: 'numeric' });
  }, []);

  const formatPrice = (num) => num.toLocaleString('en-US', { maximumFractionDigits: 2 });

  const generateSubHead = useCallback((point, period) => {
    const firstPoint = sparklineInteractiveData[period][0];
    const increase = point.value > firstPoint.value;
    return {
      percent: `${formatPrice(Math.abs((point.value - firstPoint.value) / firstPoint.value) * 100)}%`,
      sign: increase ? 'upwardTrend' : 'downwardTrend',
      variant: increase ? 'positive' : 'negative',
      priceChange: `$${formatPrice(Math.abs(point.value - firstPoint.value))}`,
    };
  }, []);

  const headerRef = useRef(null);
  const [currentPeriod, setCurrentPeriod] = useState('day');
  const data = sparklineInteractiveData[currentPeriod];
  const lastPoint = data[data.length - 1];

  const handleScrub = useCallback(
    ({ point, period }) => {
      headerRef.current?.update({
        title: `$${point.value.toLocaleString('en-US')}`,
        subHead: generateSubHead(point, period),
      });
    },
    [generateSubHead],
  );

  const handleScrubEnd = useCallback(() => {
    headerRef.current?.update({
      title: `$${formatPrice(lastPoint.value)}`,
      subHead: generateSubHead(lastPoint, currentPeriod),
    });
  }, [lastPoint, currentPeriod, generateSubHead]);

  const handlePeriodChanged = useCallback(
    (period) => {
      setCurrentPeriod(period);
      const newData = sparklineInteractiveData[period];
      const newLastPoint = newData[newData.length - 1];
      headerRef.current?.update({
        title: `$${formatPrice(newLastPoint.value)}`,
        subHead: generateSubHead(newLastPoint, period),
      });
    },
    [generateSubHead],
  );

  return (
    <Box padding={3} width="100%">
      <SparklineInteractive
        data={sparklineInteractiveData}
        defaultPeriod="day"
        formatDate={formatDate}
        headerNode={
          <SparklineInteractiveHeader
            ref={headerRef}
            defaultLabel="CustomHeader"
            defaultSubHead={generateSubHead(lastPoint, currentPeriod)}
            defaultTitle={`$${formatPrice(lastPoint.value)}`}
            labelNode={
              <HStack gap={1} alignItems="center">
                <Icon active name="wallet" size="s" />
                <Text as="span" font="title3">
                  CustomHeader
                </Text>
              </HStack>
            }
          />
        }
        onPeriodChanged={handlePeriodChanged}
        onScrub={handleScrub}
        onScrubEnd={handleScrubEnd}
        periods={periods}
        strokeColor="#F7931A"
      />
    </Box>
  );
};
```

## Props

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `defaultTitle` | `null \| string \| number \| bigint \| false \| true \| ReactElement<unknown, string \| JSXElementConstructor<any>> \| Iterable<ReactNode> \| ReactPortal \| Promise<AwaitedReactNode>` | Yes | `-` | Default title, changing this prop has no effect once the default is rendered. If you use a ReactNode that is not a string, then you cannot use the text based label that supports updates. |
| `compact` | `boolean` | No | `-` | Reduce the font size used for the header itself. |
| `defaultLabel` | `string` | No | `-` | Default label, changing this prop has no effect once the default is rendered. |
| `defaultSubHead` | `SparklineInteractiveSubHead` | No | `-` | Default SubHead, changing this prop has no effect once the default is rendered. |
| `key` | `Key \| null` | No | `-` | - |
| `labelNode` | `null \| string \| number \| bigint \| false \| true \| ReactElement<unknown, string \| JSXElementConstructor<any>> \| Iterable<ReactNode> \| ReactPortal \| Promise<AwaitedReactNode>` | No | `-` | Adds a label node that allows React components. If you use this node then you cannot use the text based label that supports updates. |
| `ref` | `null \| RefObject<HTMLButtonElement \| null> \| (instance: HTMLButtonElement \| null) => void \| (() => VoidOrUndefinedOnly)` | No | `-` | Allows getting a ref to the component instance. Once the component unmounts, React will set ref.current to null (or call the ref with null if you passed a callback ref). |
| `testID` | `string` | No | `-` | Used to locate this element in unit and end-to-end tests. Under the hood, testID translates to data-testid on Web. On Mobile, testID stays the same - testID |


