Skip to content
ShineUI
Install

BlockUI

Freeze while it posts.

<sui-block-ui>FeedbackStable

About

Freezes a region while something posts: a translucent veil with a spinner and a short message over a card, a table or a form. Set blocked from app state when the request starts and clear it when it settles. The veil covers only what it wraps; there is no scroll lock and no focus trap.

Usage

<sui-block-ui message="Posting entry"> <div style="background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:18px;display:flex;flex-direction:column;gap:10px;"> <div style="font-size:14.5px;font-weight:700;">Journal entry JE-0331</div> <div style="font-size:13px;color:var(--ink-muted);line-height:1.6;">Dr 6100 Rent $6,000.00<br />Cr 1000 Operating Checking $6,000.00</div> <sui-button variant="outline" label="Edit lines"></sui-button> </div> </sui-block-ui> <script type="module"> import { defineCustomElements } from "@shineui/core/loader"; import "@shineui/core/shineui.css"; defineCustomElements(); </script>
import { SuiBlockUi } from "@shineui/react"; import "@shineui/core/shineui.css"; export function Example() { return ( <SuiBlockUi message="Posting entry"> <div style="background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:18px;display:flex;flex-direction:column;gap:10px;"> <div style="font-size:14.5px;font-weight:700;">Journal entry JE-0331</div> <div style="font-size:13px;color:var(--ink-muted);line-height:1.6;">Dr 6100 Rent $6,000.00<br />Cr 1000 Operating Checking $6,000.00</div> <sui-button variant="outline" label="Edit lines"></sui-button> </div> </SuiBlockUi> ); }
<script setup> import { SuiBlockUi } from "@shineui/vue"; import "@shineui/core/shineui.css"; </script> <template> <SuiBlockUi message="Posting entry"><div style="background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:18px;display:flex;flex-direction:column;gap:10px;"> <div style="font-size:14.5px;font-weight:700;">Journal entry JE-0331</div> <div style="font-size:13px;color:var(--ink-muted);line-height:1.6;">Dr 6100 Rent $6,000.00<br />Cr 1000 Operating Checking $6,000.00</div> <sui-button variant="outline" label="Edit lines"></sui-button> </div></SuiBlockUi> </template>
import { Component } from "@angular/core"; import { SuiBlockUi } from "@shineui/angular"; @Component({ standalone: true, imports: [SuiBlockUi], template: `<sui-block-ui message="Posting entry"><div style="background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:18px;display:flex;flex-direction:column;gap:10px;"> <div style="font-size:14.5px;font-weight:700;">Journal entry JE-0331</div> <div style="font-size:13px;color:var(--ink-muted);line-height:1.6;">Dr 6100 Rent $6,000.00<br />Cr 1000 Operating Checking $6,000.00</div> <sui-button variant="outline" label="Edit lines"></sui-button> </div></sui-block-ui>`, }) export class Example { }

Basic

blocked is a plain boolean from app state: up when the request starts, down when it settles. While blocked the host carries aria-busy and the content goes inert, so a keyboard user cannot Tab past the veil into the form it guards. Going inert also moves focus to the body, so a user mid-form loses their place.

Journal entry JE-0331
Dr 6100 Rent $6,000.00
Cr 1000 Operating Checking $6,000.00
<div style="display:flex;flex-direction:column;gap:12px;"> <div style="display:flex;gap:8px;flex-wrap:wrap;"> <sui-button id="bui-post" variant="accent" label="Post entry"></sui-button> <sui-button id="bui-release" variant="ghost" label="Release"></sui-button> </div> <sui-block-ui id="bui-live" message="Posting entry"> <div style="background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:18px;display:flex;flex-direction:column;gap:10px;"> <div style="font-size:14.5px;font-weight:700;">Journal entry JE-0331</div> <div style="font-size:13px;color:var(--ink-muted);line-height:1.6;">Dr 6100 Rent $6,000.00<br />Cr 1000 Operating Checking $6,000.00</div> <sui-button variant="outline" label="Edit lines"></sui-button> </div> </sui-block-ui> </div>
// setup(root): properties and events setup(root) { const block = root.querySelector('#bui-live'); root.querySelector('#bui-post').addEventListener('click', () => { block.blocked = true; setTimeout(() => { block.blocked = false; }, 2400); }); root.querySelector('#bui-release').addEventListener('click', () => { block.blocked = false; }); }

Veil slot

Fill the veil slot to put something else under the veil; a determinate sui-progress-bar is the case it exists for. The slot replaces the default spinner, message is not read while it is filled, and the slot sits outside the inert region, so its content stays reachable while the content underneath is not.

340 of 1,200
Journal entry JE-0331
Dr 6100 Rent $6,000.00
Cr 1000 Operating Checking $6,000.00
<sui-block-ui blocked> <div slot="veil" style="width:280px;max-width:80%;"> <sui-progress-bar label="Importing bank feed" value="28" show-value><span slot="value">340 of 1,200</span></sui-progress-bar> </div> <div style="background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:18px;"> <div style="font-size:14.5px;font-weight:700;margin-bottom:8px;">Journal entry JE-0331</div> <div style="font-size:13px;color:var(--ink-muted);line-height:1.6;">Dr 6100 Rent $6,000.00<br />Cr 1000 Operating Checking $6,000.00</div> </div> </sui-block-ui>

Around a table

The veil is absolute inside the wrapper, so it covers exactly the region handed to it: a table, a card, a form column. Blocking a region taller than the viewport centers the spinner on the region, not the screen; wrap the app shell if the whole page has to freeze.

AccountDebitCredit
6100 Rent$6,000.00
1000 Operating Checking$6,000.00
<sui-block-ui blocked message="Recalculating"> <div style="background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:14px 16px;display:flex;flex-direction:column;gap:10px;"> <div style="display:grid;grid-template-columns:2fr 1fr 1fr;gap:14px;font:700 12.5px/1.4 var(--font-sans);color:var(--ink-muted);"> <span>Account</span><span>Debit</span><span>Credit</span> </div> <div style="display:grid;grid-template-columns:2fr 1fr 1fr;gap:14px;font:460 13px/1.6 var(--font-sans);"> <span>6100 Rent</span><span>$6,000.00</span><span></span> </div> <div style="display:grid;grid-template-columns:2fr 1fr 1fr;gap:14px;font:460 13px/1.6 var(--font-sans);"> <span>1000 Operating Checking</span><span></span><span>$6,000.00</span> </div> </div> </sui-block-ui>

API

<sui-block-ui>

Properties

PropertyAttributeTypeDefaultDescription
blockedblockedbooleanfalseShows the veil. While true the host carries aria-busy="true" and the wrapped content is inert, so it leaves the tab order as well as the pointer's reach. A browser moves focus to the body when the element holding it goes inert, so a user mid-form loses their place; nothing here catches that.
messagemessagestringstring | undefinedCaption under the spinner. Empty string hides it. Ignored when the veil slot is filled.

Slots

SlotDescription
(default)The content being blocked (a card, a table, a form).
veilReplaces the default spinner and message on the veil; a determinate sui-progress-bar is the case it exists for. It sits outside the inert region, so its content stays reachable. Name it yourself: message is not read when this is filled.

Theming

Every design token this element can render with, read from its own stylesheets and the ones it imports. Set a token on :root or on any ancestor and every use below follows it. The values shown are the ones in the theme you are viewing. The list is read per stylesheet, so an element that imports a shared drawing module lists that module's tokens too, whether or not it paints with all of them.

See also