Getting Started
Nexub DS works under a strict Content-Security-Policy with no 'unsafe-inline', hashes or nonces:
default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'@nexub/react injects no <style> tags of its own, and its CSS ships in @nexub/themes. A few components use Radix primitives that inject one when they open; the table says how to use those.
Everything not listed here is CSP-safe as-is, including SettingsLayout, Popover, DatePicker, Combobox, Tooltip and Toast.
| Component | What happens | Use it safely |
|---|---|---|
Sheet / Drawer | The default scroll lock injects a <style> sized to the scrollbar, so no hash can allow it. | Pass scrollLock="class" on Root. SettingsLayout does this for its phone menu. |
DropdownMenu, ContextMenu | In the default modal mode, the scroll lock injects a <style>. | Pass modal={false} on Root. |
Dialog, AlertDialog, CommandPalette, Select | The scroll-lock <style> is blocked: a console error, and the page behind can scroll while it's open. Focus, keyboard and dismissal still work. | Fine for most uses. For panels and menus, use a Sheet with scrollLock="class". |
ScrollArea, Select | Radix adds a fixed <style> that hides the native scrollbar. @nexub/themes/base ships the same rule, so it looks right; the blocked tag only logs a console error. | Nothing to do. To silence the error, allow the hash the browser prints in it. |
<Sheet.Root open={open} onOpenChange={setOpen} direction="left" scrollLock="class">
<Sheet.Portal>
<Sheet.Overlay />
<Sheet.Content side="left">…</Sheet.Content>
</Sheet.Portal>
</Sheet.Root>scrollLock="class" adds nexub-scroll-lock (from @nexub/themes/base) to <html> while the overlay is shown. The sheet is still modal: focus stays inside, the rest of the page is hidden from screen readers, and Escape or a click outside closes it.
@nexub/themes/base bundles Plus Jakarta Sans. Every font file it references is over 8 KB, so Vite (4 KB limit) and webpack (8 KB) emit real files instead of data: URLs, and font-src 'self' works with default settings.
To make sure no font is ever inlined, including your own, add this to your Vite config:
// vite.config.ts
export default defineConfig({
build: {
// Never inline fonts as data: URLs (font-src 'self'); other assets keep the default.
assetsInlineLimit: (file) => (/\.(woff2?|ttf|otf)$/.test(file) ? false : undefined),
},
});assetsInlineLimit: 0 also works but turns inlining off for every asset.
To use the system font instead, override the font token. Browsers only download a font a page uses, so the bundled one is never fetched:
:root {
--font-family-sans: ui-sans-serif, system-ui, sans-serif;
}Vite inlines small images as data: URLs, which img-src 'self' data: allows. Serve @nexub/brand files from your own origin (see Using the brand assets).
Some components set positions and sizes through React's style prop. In the browser that goes through the CSSOM, which CSP allows. Server-rendered HTML is different: a style="…" attribute in the markup is blocked. Client-rendered apps (like a Vite SPA) aren't affected.