remix/ui/jsx-runtime
Runtime UI primitives for Remix apps, including the component runtime, server rendering, frame hydration, reusable mixins, first-party components, and theme tokens.
Features
- Component runtime APIs for rendering, hydration, frame navigation, and JSX
- Server rendering APIs for streaming Remix UI trees and frames
mixcomposition with event, ref, CSS, and animation helpers- First-party components such as buttons, menus, listboxes, popovers, and selects
- Fixed typed
themecontract whose leaves resolve tovar(--rmx-...) createTheme()andcreateGlyphSheet()utilities for shared app styling and glyphs
Installation
npm i remixUsage
Define your app theme once:
import { createTheme } from 'remix/ui'
let Theme = createTheme({
space: {
none: '0px',
px: '1px',
xs: '2px',
sm: '4px',
md: '8px',
lg: '12px',
xl: '16px',
xxl: '24px',
},
radius: {
none: '0px',
sm: '4px',
md: '8px',
lg: '12px',
xl: '16px',
full: '9999px',
},
fontSize: {
xxxs: '10px',
xxs: '11px',
xs: '12px',
sm: '14px',
md: '16px',
lg: '18px',
xl: '20px',
xxl: '28px',
},
lineHeight: {
tight: '1.2',
normal: '1.5',
relaxed: '1.7',
},
fontWeight: {
normal: '400',
medium: '500',
semibold: '600',
bold: '700',
},
shadow: {
xs: '0 1px 2px rgb(0 0 0 / 0.05)',
sm: '0 1px 3px rgb(0 0 0 / 0.10)',
md: '0 4px 10px rgb(0 0 0 / 0.12)',
lg: '0 10px 30px rgb(0 0 0 / 0.16)',
xl: '0 20px 50px rgb(0 0 0 / 0.20)',
},
zIndex: {
dropdown: '1000',
popover: '1100',
sticky: '1200',
overlay: '1300',
modal: '1400',
toast: '1500',
tooltip: '1600',
},
surface: {
lvl0: '#ffffff',
lvl1: '#f8fafc',
lvl2: '#f1f5f9',
lvl3: '#e5edf7',
lvl4: '#dbe6f4',
},
colors: {
text: {
primary: '#111827',
secondary: '#374151',
muted: '#6b7280',
link: '#2563eb',
},
border: {
subtle: '#e5e7eb',
default: '#d1d5db',
strong: '#9ca3af',
},
focus: {
ring: '#3b82f6',
},
overlay: {
scrim: 'rgb(0 0 0 / 0.45)',
},
action: {
primary: {
background: '#2563eb',
backgroundHover: '#1d4ed8',
backgroundActive: '#1e40af',
foreground: '#ffffff',
border: '#2563eb',
},
secondary: {
background: '#ffffff',
backgroundHover: '#f8fafc',
backgroundActive: '#f1f5f9',
foreground: '#111827',
border: '#d1d5db',
},
danger: {
background: '#dc2626',
backgroundHover: '#b91c1c',
backgroundActive: '#991b1b',
foreground: '#ffffff',
border: '#dc2626',
},
},
},
})Render the theme once near the top of your document:
import type { Handle, RemixNode } from 'remix/ui'
function Layout(handle: Handle<{ children: RemixNode }>) {
return () => (
<html>
<head>
<Theme />
</head>
<body>{handle.props.children}</body>
</html>
)
}Consume the shared token contract from app code and first-party components:
import { css } from 'remix/ui'
import { theme } from 'remix/ui'
let card = css({
backgroundColor: theme.surface.lvl0,
color: theme.colors.text.primary,
border: `1px solid ${theme.colors.border.subtle}`,
borderRadius: theme.radius.md,
paddingInline: theme.space.md,
paddingBlock: theme.space.sm,
})
<div mix={card} />Render shared glyphs separately from the theme styles:
import type { Handle, RemixNode } from 'remix/ui'
import { Button } from 'remix/ui/button'
import { Glyph } from 'remix/ui/glyph'
import { RMX_01, RMX_01_GLYPHS } from 'remix/ui/theme'
function Layout(handle: Handle<{ children: RemixNode }>) {
return () => (
<html>
<head>
<RMX_01 />
</head>
<body>
<RMX_01_GLYPHS />
<Button startIcon={<Glyph name="add" />} tone="primary">
New project
</Button>
{handle.props.children}
</body>
</html>
)
}Cascade Layers
Remix UI emits its built-in theme reset in rmx-reset and generated css(...) rules under rmx. Unlayered CSS outranks layered component CSS, so use explicit layer order when mixing Remix UI with global styles.
Put layers that should lose to Remix UI before rmx-reset and rmx:
@layer base, rmx-reset, rmx;
@layer base {
button,
input,
textarea,
select {
font: inherit;
margin: 0;
padding: 0;
}
}License
See LICENSE