# Loading for Stripe Apps
Show loading indicators so people know your app is working and the interface stays responsive.
Loading states communicate that your app is retrieving data. A well-implemented loading pattern builds trust by keeping the interface responsive and preventing layout shifts when content appears.
## Before you begin
Before you implement loading patterns, make sure your app meets these requirements:
- [Create an app](https://docs.stripe.com/stripe-apps/create-app.md) or use an existing one.
- Install `@stripe/ui-extension-sdk` version `9.x` or later.
- Review the [Spinner](https://docs.stripe.com/stripe-apps/components/spinner.md) component reference.
## Best practices
- **Immediate feedback**: Show a loading indicator as soon as a data fetch begins.
- **Appropriate scope**: Match the loading indicator to the scope of what’s loading. Use the full-page spinner for entire views, and inline spinners for sections.
- **Prevent flash**: Use the `delay` prop on the Spinner to avoid showing indicators for fast operations.
> Some components handle loading states internally through built-in props. For example, Button uses a `pending` prop instead of requiring a manual Spinner. Before you build a custom loading pattern, review the component reference to see if built-in loading behavior is available.
## Show a full-page loading state
Show a centered Spinner with `size="large"` that fills the available space when your entire view depends on a single data fetch.
```jsx
if (isLoading) {
return (
);
}
```
## Show a section-level loading state
Use `size="medium"` when other parts of the page are already visible. If needed, load sections independently so available content appears immediately while other sections finish loading.
## Use the delay prop to prevent flash
Use `` to prevent the spinner from appearing if data arrives within the delay window.
**Recommended delay values:**
- **200–300ms** for most data fetches
- **0ms** for known-slow operations
- **100ms** for view transitions
## Handle loading in tabbed layouts
Place the loading state inside each tab component, not around the `Tabs` container. Keep the tab bar visible and interactive.
> Don’t wrap `Tabs` in a loading state. If you show a spinner instead of the tab bar, users lose their navigation context and might think the app is broken instead of loading.
## Handle loading for actions
When a user triggers an action like saving or submitting, use the [Button](https://docs.stripe.com/stripe-apps/components/button.md) component’s built-in `pending` prop. This sets the button to a pending visual style and disables it, which helps prevent double submission without requiring you to manually compose a Spinner.
```jsx
import {useState} from 'react';
import {Button} from '@stripe/ui-extension-sdk/ui';
const SaveButton = ({onSave}) => {
const [isSaving, setIsSaving] = useState(false);
const handleSave = async () => {
setIsSaving(true);
try {
await onSave();
} finally {
setIsSaving(false);
}
};
return (
);
};
```
The `pending` prop handles the loading indicator and disabled state together—you don’t need to add a Spinner inside the button manually.
## Choose the right spinner size
| Size | Use case |
| --- | --- |
| `large` | Full-page or full-tab loading. Someone is waiting for primary content. |
| `medium` | Section-level loading. Other parts visible. |
| `small` | Inline indicators within table cells or compact UI elements. |
## See also
- [Spinner component for Stripe Apps](https://docs.stripe.com/stripe-apps/components/spinner.md)
- [Button component for Stripe Apps](https://docs.stripe.com/stripe-apps/components/button.md)
- [Full-page apps](https://docs.stripe.com/stripe-apps/patterns/full-page-apps.md)