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

ValueBehavior
'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:

PatternExample
Block.hkb-button, .hkb-card
Modifier.hkb-button--destructive, .hkb-badge--success
Element.hkb-card-header, .hkb-endpoint-url

Key Component Classes

ComponentRoot 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.