Styling
The portal ships with a complete CSS file and supports theming via a theme prop or CSS variables.
Default Styles
Import the bundled stylesheet:
import '@hookbase/portal/styles.css';This provides all component styles, dark mode, and a CSS reset scoped to .hookbase-portal.
Theme Prop
Pass a theme object to <HookbasePortal> for JavaScript-based customization:
<HookbasePortal
token={token}
theme={{
colors: {
primary: '#6366f1',
background: '#ffffff',
foreground: '#111827',
success: '#22c55e',
destructive: '#ef4444',
},
borderRadius: '0.75rem',
darkMode: 'auto',
}}
>
{children}
</HookbasePortal>HookbaseTheme
interface HookbaseTheme {
colors?: {
primary?: string; // Primary buttons, links, focus rings
background?: string; // Page/card background
foreground?: string; // Default text color
muted?: string; // Muted backgrounds (inputs, skeletons)
mutedForeground?: string; // Secondary text
border?: string; // Card/input borders
destructive?: string; // Delete buttons, error badges
success?: string; // Success badges, verified indicators
warning?: string; // Warning badges, pending status
};
borderRadius?: string; // Border radius for cards, buttons, inputs
darkMode?: 'auto' | 'light' | 'dark'; // Dark mode preference
}Dark Mode
| Value | Behavior |
|---|---|
'auto' | Follows prefers-color-scheme media query |
'light' | Always light mode |
'dark' | Always dark mode |
When dark mode is active, the portal wrapper gets a .dark class and CSS variables switch to dark values.
CSS Variables
All components use CSS custom properties prefixed with --hkb-. Override them in your stylesheet for complete control:
.hookbase-portal {
/* Colors (HSL values) */
--hkb-primary: 222 47% 11%;
--hkb-primary-foreground: 210 40% 98%;
--hkb-background: 0 0% 100%;
--hkb-foreground: 222 47% 11%;
--hkb-muted: 210 40% 96%;
--hkb-muted-foreground: 215 16% 47%;
--hkb-border: 214 32% 91%;
--hkb-input: 214 32% 91%;
--hkb-ring: 222 47% 11%;
--hkb-success: 142 76% 36%;
--hkb-success-foreground: 0 0% 100%;
--hkb-warning: 38 92% 50%;
--hkb-warning-foreground: 0 0% 0%;
--hkb-destructive: 0 84% 60%;
--hkb-destructive-foreground: 0 0% 100%;
/* Layout */
--hkb-radius: 0.5rem;
}Dark Mode Variables
.hookbase-portal.dark {
--hkb-primary: 210 40% 98%;
--hkb-primary-foreground: 222 47% 11%;
--hkb-background: 222 47% 11%;
--hkb-foreground: 210 40% 98%;
--hkb-muted: 217 33% 17%;
--hkb-muted-foreground: 215 20% 65%;
--hkb-border: 217 33% 17%;
--hkb-input: 217 33% 17%;
--hkb-ring: 212 100% 67%;
--hkb-success: 142 70% 45%;
--hkb-warning: 38 92% 50%;
--hkb-destructive: 0 62% 54%;
}CSS Class Naming
All CSS classes use the hkb- prefix and BEM-like naming:
| Pattern | Example |
|---|---|
| Block | .hkb-button, .hkb-card |
| Modifier | .hkb-button--destructive, .hkb-badge--success |
| Element | .hkb-card-header, .hkb-endpoint-url |
Key Component Classes
| Component | Root Class |
|---|---|
| Button | .hkb-button |
| Card | .hkb-card |
| Badge | .hkb-badge |
| Dialog | .hkb-dialog-content |
| Input | .hkb-input |
| Textarea | .hkb-textarea |
| Skeleton | .hkb-skeleton |
| Switch | .hkb-switch |
| Checkbox | .hkb-checkbox |
| EndpointList | .hkb-endpoint-list |
| EndpointForm | .hkb-endpoint-form |
| EventTypeList | .hkb-event-type-list |
| SubscriptionManager | .hkb-subscription-manager |
| MessageLog | .hkb-message-log-card |
Headless Mode
Skip the stylesheet entirely and use the hooks to build your own UI. All data fetching, caching, and mutations work without the default components:
import { HookbasePortal, useEndpoints, useTestEndpoint } from '@hookbase/portal';
// No styles.css import
function CustomPortal({ token }) {
return (
<HookbasePortal token={token}>
<CustomEndpointList />
</HookbasePortal>
);
}
function CustomEndpointList() {
const { endpoints, isLoading, deleteEndpoint } = useEndpoints();
const { testEndpoint, isTesting, testResult } = useTestEndpoint();
if (isLoading) return <MySpinner />;
return (
<MyList>
{endpoints.map(ep => (
<MyListItem key={ep.id}>
<span>{ep.url}</span>
<MyBadge color={ep.isVerified ? 'green' : 'gray'}>
{ep.isVerified ? 'Verified' : 'Unverified'}
</MyBadge>
<button onClick={() => testEndpoint(ep.id)} disabled={isTesting}>
Test
</button>
<button onClick={() => deleteEndpoint(ep.id)}>
Delete
</button>
</MyListItem>
))}
</MyList>
);
}className Prop
All portal components accept a className prop for adding custom classes:
<EndpointList className="my-endpoint-list" />
<MessageLog className="my-messages" />
<SubscriptionManager className="my-subscriptions" />Scoping
All portal styles are scoped under the .hookbase-portal selector, so they won't conflict with your application's global styles. The portal also includes a box-sizing reset within its scope.