UI System
Use @putnami/ui for server-renderable React components, responsive layout,
light/dark theming, interaction semantics, and sanitized rich content. Routing,
loaders, actions, and authorization belong to @putnami/web and
@putnami/application.
Installation
putnami deps add @putnami/uiStart with the theme
import { Button, Container, Heading, Text, ThemeProvider, VStack } from '@putnami/ui';
export function App() {
return (
<ThemeProvider>
<Container maxWidth='lg'>
<VStack gap='md'>
<Heading level={1}>Account</Heading>
<Text color='secondary'>Manage your profile.</Text>
<Button colorScheme='primary'>Save</Button>
</VStack>
</Container>
</ThemeProvider>
);
}ThemeProvider accepts light, dark, or system. It emits one bootstrap
script that resolves the persisted/system preference before paint and marks the
root element with data-color-mode. Existing component classes follow later
mode changes because mode-dependent colors use semantic CSS custom properties.
When writing custom Emotion styles, use var(--color-*) for mode-dependent
colors. Values read from theme.colors.* are serialized into the SSR class and
will not change merely because data-color-mode changes. Typed spacing, radii,
typography, breakpoints, and other mode-independent tokens remain safe to read
from the theme object.
Responsive layout
Layout primitives include Box, Flex, Grid, Stack, HStack, VStack,
Container, and Page. Responsive arrays are mobile-first:
import { Box, Grid } from '@putnami/ui';
<Box p={['sm', 'md', 'lg']}>
<Grid columns={[1, 2, 3]} gap='md'>
{/* cards */}
</Grid>
</Box>;The first value is the base rule; later values map to sm, md, lg, xl,
and xxl. Null values skip a breakpoint and values beyond the catalog are
ignored. Components never need to read the browser viewport during render, so
server and browser emit the same CSS.
Components and interaction semantics
The public component families are:
- typography and data display:
Heading,Text,Card,Table,Badge,Avatar,Divider,CodeBlock; - form and feedback:
Button,Input,Alert,Progress,Spinner,ToastProvider,useToast; - navigation:
Link,Tabs,Breadcrumb,Pagination; - overlays:
Modal,Drawer,Dropdown,Popover,Tooltip; - icons: built-in SVG components plus
createIcon(); - Emotion integration: typed
styled,css, andkeyframes.
Loading buttons expose busy and disabled state. Modal/drawer surfaces expose dialog semantics only while open. Tabs expose selection and roving-tabindex state and support Arrow, Home, and End navigation across enabled items. Overlay focus helpers wrap Tab at the surface boundary and support Escape/outside-click dismissal.
These primitives do not make arbitrary application composition accessible by themselves. Provide meaningful labels, content order, trigger relationships, and initial focus for your workflow.
Safe rich content
MarkdownRenderer accepts pre-rendered HTML and sanitizes it at the raw HTML
sink:
import { MarkdownRenderer } from '@putnami/ui';
<MarkdownRenderer html={renderedMarkdown} />;Its allowlist preserves structural headings, lists, tables, code blocks,
documented Shiki variables, code-group/Mermaid data attributes, and aria-*
attributes. It removes scripts/styles and their contents, embedded browsing
contexts, comments, event attributes, unsafe inline styles, and dangerous URL
schemes after decoding entity/control-character obfuscation. New browsing
contexts receive rel="noopener noreferrer".
MarkdownContent is only a styled container. If you inject raw HTML into it,
sanitize explicitly:
import { MarkdownContent, sanitizeHtml } from '@putnami/ui';
<MarkdownContent dangerouslySetInnerHTML={{ __html: sanitizeHtml(renderedMarkdown) }} />;Support and compatibility
@putnami/ui is a public, documented, maintained package classified stable.
Its UI-system specification and accepted ADRs for SSR color mode and rich
content live next to the package source. The promise covers deterministic
server/browser styling, mobile-first responsive compilation, the documented
semantics and focus/keyboard behavior of interactive primitives, and the
allowlist boundary used by MarkdownRenderer.
UI visibility never grants route access; enforce authorization on the server.
The package makes no default-framework or cross-language parity claim. Before
v1.0.0, minor 0.x releases may contain documented breaking changes.