## Dash Loading States Learn how to show loading states in Dash apps using DMC components like Loader, Skeleton, and LoadingOverlay. Includes integration with dcc.Loading and examples of custom CSS-based spinners. Category: Dash ### DMC Loading components DMC has several components that are often used to indicate a component's loading state: - [Loader](/components/loader): A basic visual spinner (oval, bars, or dots) for simple loading states. Can be customized with size, color, and type. - [LoadingOverlay](/components/loadingoverlay): Overlays a parent element with a loader, disabling user interaction to indicate a loading state, commonly used for forms. - [Progress](/components/progress): A progress bar that shows task status and completion percentage. Customizable with value, color, label, and sections. - [Button](/components/button) and [ActionIcon](/components/actionicon) (`loading` prop): Integrates a `Loader` into buttons and icons. Disables Button while `loading=True`. - [Skeleton](/components/skeleton): Creates placeholders resembling content layout to give users a preview while loading, reducing perceived wait time. ### With dcc.Loading For greater control over when the DMC loading components are displayed and for how long, use the `dcc.Loading` component from `dash-core-components`. DMC components such as `Skeleton`, `Loader`, `LoadingOverlay` can be displayed in the `custom_spinner` prop. You can configure options such as: - `delay_show`: Specifies the wait time before displaying the loading component. This helps prevent flickering for fast-loading content. - `delay_hide`: Defines how long the loading component remains visible after loading completes, creating a smoother transition between the placeholder and final content. - `target_components`: Determines which components trigger the loader to display. This makes the loading effect triggered only by specific components rather than being triggered by any of the children. Refer to the [Dash Documentation](https://dash.plotly.com/dash-core-components/loading) for more details. Here is an example of `delay_hide` prop in `dcc.Loading` to prevent the `Skeleton` from showing for a very short time. ```python import time import dash_mantine_components as dmc from dash import Input, Output, html, callback, dcc component = html.Div( [ dcc.Loading( children=html.Div([ dmc.Text("Initial data", id="dccloading-div"), dmc.Text("The data only takes 200ms to update, but `delay_hide` is set to 1000ms to prevent flashing.") ]), delay_hide=1000, custom_spinner = dmc.Skeleton( visible=True, h="100%" ), ), dmc.Button("Update!", id="dccloading-button"), ] ) @callback( Output("dccloading-div", "children"), Input("dccloading-button", "n_clicks"), prevent_initial_call=True ) def update_graph(n): time.sleep(.2) return f"Data updated {n} times" ``` ### Custom CSS Loaders Using data-dash-is-loading Dash automatically adds the attribute `data-dash-is-loading="true"` to components that are being updated in a callback. You can use this attribute to apply custom loading indicators using CSS — no extra Python code or spinner components needed! #### Basic Example You can hide a component’s content and display a custom loader using just CSS: ```css /* Hide the content while it's loading */ [data-dash-is-loading="true"] { visibility: hidden; position: relative; } /* Show a loader */ [data-dash-is-loading="true"]::before { content: "Loading..."; visibility: visible; position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); } ``` In your Dash layout: ```python dmc.Text(id="output-id", className="my-output") ``` In your `assets/style.css`: ```css .my-output[data-dash-is-loading="true"]::before { /* Custom loader styles */ } ``` Now, whenever `output-id` is being updated by a callback, your custom loader will appear in place of the original content. ### Example with 3 custom spinners This example has 3 custom spinner defined in the `.css` file in `/assets`. The different loaders are applied to the component based on the `className`. ```python from dash import Input, Output, callback import dash_mantine_components as dmc import time component = dmc.Stack([ dmc.Title(" Dash Spinner Loader Showcase", order=2), dmc.Card([ dmc.Select( label="Pulse Loader", placeholder="Choose an option...", data=["A", "B", "C"], id="dash-is-loading-input-1" ), dmc.Text(id="dash-is-loading-output-1", className="output-example-loading", py="lg") ], className="pulse-loader", withBorder=True ), dmc.Card([ dmc.Select( label="Ring Loader", placeholder="Choose an option...", data=["A", "B", "C"], id="dash-is-loading-input-2" ), dmc.Text(id="dash-is-loading-output-2", className="output-example-loading", py="lg") ], className="ring-loader", withBorder=True), dmc.Card([ dmc.Select( label="Bounce Loader", placeholder="Choose an option...", data=["A", "B", "C"], id="dash-is-loading-input-3" ), dmc.Text(id="dash-is-loading-output-3", className="output-example-loading", py="xl") ], className="bounce-loader"), ]) @callback(Output("dash-is-loading-output-1", "children"), Input("dash-is-loading-input-1", "value")) def update_output1(value): time.sleep(2) return f"You selected: {value}" @callback(Output("dash-is-loading-output-2", "children"), Input("dash-is-loading-input-2", "value")) def update_output2(value): time.sleep(2) return f"You selected: {value}" @callback(Output("dash-is-loading-output-3", "children"), Input("dash-is-loading-input-3", "value")) def update_output3(value): time.sleep(2) return f"You selected: {value}" ``` ```css /* Output Base Hidden + Loader Target */ *[data-dash-is-loading="true"] { visibility: hidden; position: relative; } *[data-dash-is-loading="true"]::before { visibility: visible; position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); /* centers the spinner */ content: ""; } /* pulse loader */ .pulse-loader .output-example-loading[data-dash-is-loading="true"]::before { content: ""; width: 20px; height: 20px; border-radius: 50%; background-color: var(--mantine-color-blue-filled); position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%) scale(0.95); animation: pulseAnim 1s infinite ease-in-out; visibility: visible; } @keyframes pulseAnim { 0% { transform: translate(-50%, -50%) scale(0.95); opacity: 1; } 50% { transform: translate(-50%, -50%) scale(1.2); opacity: 0.6; } 100% { transform: translate(-50%, -50%) scale(0.95); opacity: 1; } } /* Loader: Ring */ .ring-loader .output-example-loading[data-dash-is-loading="true"]::before { content: ""; width: 40px; height: 40px; border: 4px solid #f3f3f3; border-top: 4px solid var(--mantine-color-green-filled); border-radius: 50%; position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%) rotate(0deg); animation: ringSpin 1s linear infinite; visibility: visible; } @keyframes ringSpin { 0% { transform: translate(-50%, -50%) rotate(0deg); } 100% { transform: translate(-50%, -50%) rotate(360deg); } } /* Loader: Bounce Dots */ .bounce-loader .output-example-loading[data-dash-is-loading="true"]::before { content: ""; display: inline-block; width: 60px; height: 16px; position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); } .bounce-loader .output-example-loading[data-dash-is-loading="true"]::before { background: transparent; } .bounce-loader .output-example-loading[data-dash-is-loading="true"]::before { content: "● ● ●"; color: var(--mantine-color-orange-filled); font-size: 22px; letter-spacing: 6px; animation: dotBounce 1.2s infinite ease-in-out; } /* Animation for Dots */ @keyframes dotBounce { 0%, 80%, 100% { opacity: 0.2; transform: translate(-50%, -50%) scale(0.8); } 40% { opacity: 1; transform: translate(-50%, -50%) scale(1.2); } } ```