Performance and Async Behavior
Boreal UI components keep timer, polling, and transient UI state local to each mounted component instance. Rendering two instances of the same component does not share or overwrite their timers.
Timed Feedback
Chip
Chip starts its auto-close countdown when visible is true and autoClose is enabled. duration controls the countdown and defaults to 3000 milliseconds. After a close begins, the component keeps its exit state for 300 milliseconds before calling onClose.
Repeated close requests during that exit window are coalesced into one callback. Pending auto-close and exit timers are cleared when the Chip becomes hidden or unmounts.
<>
<Chip
id="saved-chip"
message="Saved"
visible={showSaved}
duration={1500}
onClose={() => setShowSaved(false)}
/>
<Chip
id="synced-chip"
message="Synced"
visible={showSynced}
duration={3000}
onClose={() => setShowSynced(false)}
/>
</>
Each Chip owns its own timer. Give rendered elements unique id values so HTML and ARIA references also remain unique.
ToastProvider
Toast timers are owned by the nearest ToastProvider. Adding a toast with an existing id replaces the previous toast and restarts its expiry timer from the replacement's duration.
Use stable IDs when an event should update an existing toast, such as changing an upload from pending to complete. Omit the ID when every event should create a separate toast.
const { addToast } = useToast();
addToast({
id: "profile-save",
message: "Saving profile...",
duration: 0,
});
addToast({
id: "profile-save",
message: "Profile saved",
duration: 2500,
});
duration: 0 keeps a toast visible until it is removed or replaced.
Modal
Modal coalesces repeated overlay, Escape, and close-button requests while its 200-millisecond exit transition runs. onClose is called once after the transition. The pending callback and animation-frame work are canceled if the Modal unmounts.
Polling
NotificationCenter
When fetchNotifications is provided, NotificationCenter loads immediately. If pollInterval is greater than zero, it schedules the next load only after the current request settles. Slow requests therefore do not overlap or accumulate.
Notification expiry timers are scoped to each NotificationCenter instance. A notification ID must be unique within one center, but a separate center can use the same ID without affecting the first center's timer.
<NotificationCenter
notifications={notifications}
onRemove={removeNotification}
fetchNotifications={loadNotifications}
pollInterval={10_000}
/>
Changing onRemove does not restart existing expiry deadlines; an expiring notification calls the latest callback. Set pollInterval={0} to perform the initial load without scheduling later polls.
Select
Select follows the same completion-based polling model when asyncOptions is supplied. It loads immediately, waits for that request to settle, and then waits pollInterval milliseconds before starting another request. Polling stops on unmount.
<Select
aria-label="Project"
value={projectId}
onChange={setProjectId}
options={[]}
asyncOptions={loadProjects}
pollInterval={30_000}
/>
Equivalent option results do not replace internal state, which avoids unnecessary render work. Equality is based on each option's value, label, and disabled fields.
File Upload Completion
After an upload settles, FileUpload briefly keeps its completion progress visible before restoring the upload controls. The delayed reset, progress interval, and post-request state updates are canceled when the component unmounts.
Consumers do not need to clean up Boreal's internal upload timers. Cancellation of the actual network request remains the responsibility of the onSubmit implementation when that behavior is required.
DataTable Rendering
For client-side pagination, index-based fallback selection keys are calculated across the complete dataset rather than restarting on each page. For sorting, server pagination, virtualization, or mutable datasets, provide a stable rowKey so selection and expansion continue to identify the intended record.
<DataTable
columns={columns}
data={rows}
rowKey={(row) => row.id}
pagination
selectableRows
/>
Keep large columns, data, and controlled key arrays referentially stable with module-level constants or useMemo when their values have not changed. Standalone component imports also provide the clearest bundle boundary:
import DataTable from "@boreal-ui/next/DataTable";
ThemeProvider Rendering
ThemeProvider compares custom schemes by serialized value, so a parent that recreates an equivalent array does not rebuild the available scheme list. Keeping a large custom scheme collection stable with a module-level constant or useMemo still avoids repeated serialization work.
Set enableThemeScript={false} when the app already applies server-generated theme attributes. With the option disabled, ThemeProvider does not generate or render the pre-hydration script. The Next provider defaults this option to false; the core provider defaults it to true.
See Styling and Theming for SSR setup and theme persistence options.
Next.js Scroll Restoration
Boreal globals do not set smooth scrolling on the root element, leaving scroll-behavior at the browser default (auto). This allows Next.js to manage scroll position during route transitions and avoids requiring data-scroll-behavior on <html> merely because Boreal's global stylesheet is imported.
Applications can still opt into smooth root scrolling. When doing so in Next.js, add data-scroll-behavior="smooth" to the root <html> element so the framework can temporarily disable smooth scrolling during navigation.