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

# Usage Quota

> Progress bars showing current usage against plan limits for each metered feature

export const UsageQuotaPreview = () => {
  const FP_THEME = {
    light: {
      surfaceCanvas: 'rgb(255 255 255)',
      surface: 'rgb(255 255 255)',
      surfaceMuted: 'rgb(244 244 245)',
      content: 'rgb(17 24 39)',
      contentSecondary: 'rgb(55 65 81)',
      contentMuted: 'rgb(107 114 128)',
      contentTertiary: 'rgb(75 85 99)',
      line: 'rgb(229 231 235)',
      destructive: 'hsl(0 84.2% 60.2%)',
      brand: 'hsl(201 77% 15%)',
      accent: 'rgb(79 70 229)'
    },
    dark: {
      surfaceCanvas: 'rgb(24 24 24)',
      surface: 'rgb(29 29 31)',
      surfaceMuted: 'rgb(37 37 40)',
      content: 'rgb(238 239 241)',
      contentSecondary: 'rgb(208 210 215)',
      contentMuted: 'rgb(138 143 152)',
      contentTertiary: 'rgb(169 173 182)',
      line: 'rgb(41 41 46)',
      destructive: 'hsl(0 65% 55%)',
      brand: 'hsl(356 56% 60%)',
      accent: 'rgb(138 130 255)'
    }
  };
  const [mode, setMode] = useState('light');
  const t = FP_THEME[mode];
  const [used, setUsed] = useState(842300);
  const [limit, setLimit] = useState(1000000);
  const [unlimited, setUnlimited] = useState(false);
  const [playgroundOpen, setPlaygroundOpen] = useState(false);
  const percentage = unlimited ? 0 : Math.min(Math.ceil(used / limit * 100), 100);
  const isOverLimit = !unlimited && used > limit;
  const sliderStyle = {
    width: '100%',
    marginTop: 6,
    accentColor: t.accent
  };
  const labelStyle = {
    fontSize: 12,
    color: t.contentMuted,
    display: 'block'
  };
  return <div>
      {}
      <div style={{
    position: 'relative',
    border: `1px solid ${t.line}`,
    borderRadius: 12,
    background: t.surfaceCanvas,
    padding: '64px 40px',
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center',
    minHeight: 340
  }}>
        <button onClick={() => setMode(mode === 'light' ? 'dark' : 'light')} aria-label="Toggle theme" style={{
    position: 'absolute',
    top: 16,
    right: 16,
    width: 36,
    height: 36,
    borderRadius: '50%',
    border: `1px solid ${t.line}`,
    background: t.surface,
    color: t.contentTertiary,
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center',
    cursor: 'pointer'
  }}>
          {mode === 'dark' ? <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
              <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
            </svg> : <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
              <circle cx="12" cy="12" r="4" />
              <path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41" />
            </svg>}
        </button>

        <div style={{
    border: `1px solid ${t.line}`,
    borderRadius: 12,
    overflow: 'hidden',
    background: t.surface,
    width: '100%',
    maxWidth: 340
  }}>
          <div style={{
    padding: 20,
    borderBottom: `1px solid ${t.line}`
  }}>
            <h3 style={{
    margin: 0,
    fontSize: 15,
    fontWeight: 500,
    color: t.content
  }}>Usage Quota</h3>
          </div>
          <div style={{
    padding: 20
  }}>
            <div style={{
    display: 'flex',
    justifyContent: 'space-between',
    marginBottom: 8
  }}>
              <span style={{
    fontSize: 13,
    color: t.content
  }}>API calls</span>
              <span style={{
    fontSize: 13,
    color: t.contentMuted
  }}>
                {used.toLocaleString()} / {unlimited ? 'Unlimited' : limit.toLocaleString()}
              </span>
            </div>
            <div style={{
    height: 8,
    borderRadius: 999,
    background: t.surfaceMuted,
    overflow: 'hidden'
  }}>
              <div style={{
    height: '100%',
    width: unlimited ? '0%' : `${percentage}%`,
    borderRadius: 999,
    background: isOverLimit ? t.destructive : t.brand
  }} />
            </div>
          </div>
        </div>
      </div>

      {}
      <details open={playgroundOpen} onToggle={e => setPlaygroundOpen(e.target.open)} style={{
    marginTop: 12
  }}>
        <style>{`summary::-webkit-details-marker { display: none; }`}</style>
        <summary style={{
    listStyle: 'none',
    cursor: 'pointer',
    display: 'flex',
    alignItems: 'center',
    gap: 10,
    padding: '10px 12px',
    border: `1px solid ${t.line}`,
    borderRadius: 8,
    background: t.surface
  }}>
          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke={t.contentMuted} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" style={{
    flexShrink: 0
  }}>
            {playgroundOpen ? <path d="m18 15-6-6-6 6" /> : <path d="m6 9 6 6 6-6" />}
          </svg>
          <div style={{
    display: 'flex',
    flexDirection: 'column',
    gap: 1
  }}>
            <span style={{
    fontSize: 13,
    fontWeight: 500,
    color: t.content
  }}>Try it yourself</span>
            <span style={{
    fontSize: 11,
    color: t.contentMuted,
    fontWeight: 400
  }}>Preview controls</span>
          </div>
        </summary>
        <div style={{
    marginTop: 10,
    border: `1px solid ${t.line}`,
    borderRadius: 10,
    background: t.surfaceMuted,
    padding: 16,
    display: 'flex',
    flexDirection: 'column',
    gap: 16
  }}>
          <label style={labelStyle}>
            Used: {used.toLocaleString()}
            <input type="range" min="0" max="1500000" step="1000" value={used} onChange={e => setUsed(Number(e.target.value))} style={sliderStyle} />
          </label>
          <label style={labelStyle}>
            Limit: {limit.toLocaleString()}
            <input type="range" min="100000" max="2000000" step="50000" value={limit} disabled={unlimited} onChange={e => setLimit(Number(e.target.value))} style={{
    ...sliderStyle,
    opacity: unlimited ? 0.4 : 1
  }} />
          </label>
          <label style={{
    display: 'flex',
    alignItems: 'center',
    gap: 8,
    fontSize: 12,
    color: t.contentMuted
  }}>
            <input type="checkbox" checked={unlimited} onChange={e => setUnlimited(e.target.checked)} style={{
    accentColor: t.accent
  }} />
            Unlimited
          </label>
        </div>
      </details>
    </div>;
};

Use **Usage Quota** to show a customer how much of each metered feature they've used against its plan limit, as a set of progress bars. Part of [Usage Widgets](/docs/exportable-ui/usage-widgets/usage-widgets).

<Tabs>
  <Tab title="Preview">
    <UsageQuotaPreview />
  </Tab>

  <Tab title="Code">
    ```tsx theme={null}
    <UsageQuota
      items={[{ id: 'feat_api_calls', name: 'API Calls', currentUsage: 720, limit: 1000, isUnlimited: false }]}
    />
    ```
  </Tab>
</Tabs>

## Supports

* One progress bar per metered feature
* Unlimited features (rendered without a limit)
* Over-limit styling
* Empty state when there is nothing to show

## Best used for

* Customer portal overview
* Account settings / usage tab
* Upgrade prompts near a limit

## Import

```tsx theme={null}
import '@flexprice/ui/style.css';
import { UsageQuota, type UsageQuotaItem } from '@flexprice/ui';
```

## Usage

```tsx theme={null}
const items: UsageQuotaItem[] = [
  { id: 'feat_api_calls', name: 'API Calls', currentUsage: 8200, limit: 10000, isUnlimited: false },
  { id: 'feat_storage', name: 'Storage', currentUsage: 42, limit: null, isUnlimited: true },
];

<UsageQuota items={items} />
```

Only metered features have a quota to show: filter your data to metered entitlements before mapping into `items`. See [Ways to provide data](/docs/exportable-ui/overview#ways-to-provide-data) for SDK, API, backend, and static JSON options.

## Map Flexprice API data

```ts theme={null}
import { adaptUsageQuotaItems } from '@flexprice/ui';

const items = adaptUsageQuotaItems(usageSummary.features);
```

## See also

<CardGroup cols={2}>
  <Card icon="puzzle-piece" href="/docs/exportable-ui/usage-widgets/usage-widgets" title="Usage Widgets">
    Overview of all four usage components.
  </Card>

  <Card icon="table-list" href="/docs/exportable-ui/usage-widgets/usage-breakdown" title="Usage Breakdown">
    Grouped, sortable table of usage and cost by feature.
  </Card>

  <Card icon="chart-simple" href="/docs/exportable-ui/usage-widgets/metric-cards" title="Metric Cards">
    Grid of revenue, cost, margin, and custom usage metrics.
  </Card>
</CardGroup>
