@mochabug/adapt-astro
@mochabug/adapt-astro
Astro component for Adapt.
npm install @mochabug/adapt-astro
Requires Astro 3, 4, 5, or 6.
Quickstart
---
import AdaptAutomation from '@mochabug/adapt-astro/AdaptAutomation.astro';
---
<AdaptAutomation automationId="auto-123" style="height: 600px;" />
With authentication:
<AdaptAutomation automationId="auto-123" authToken="your-token" style="height: 600px;" />
With proof-of-work challenge:
<AdaptAutomation automationId="auto-123" requiresChallenge style="height: 600px;" />
SSR
Keep auth token on server. Enable SSR in astro.config.mjs:
// astro.config.mjs
export default defineConfig({
output: 'server'
});
---
import AdaptAutomation from '@mochabug/adapt-astro/AdaptAutomation.astro';
import { startSession } from '@mochabug/adapt-core';
const authToken = await getAuthTokenFromBackend();
const { token } = await startSession({ id: 'auto-123' }, authToken);
---
<AdaptAutomation automationId="auto-123" sessionToken={token} style="height: 600px;" />
Session inheritance
<!-- from URL hash: example.com#mb_session=xxx -->
<AdaptAutomation automationId="auto-123" inheritFrom={{ hash: 'mb_session' }} />
<!-- from URL param: example.com?token=xxx -->
<AdaptAutomation automationId="auto-123" inheritFrom={{ param: 'token' }} />
Fork display
<!-- side-by-side (default) -->
<AdaptAutomation automationId="auto-123" forkDisplay={{ mode: 'side-by-side', split: 60 }} />
<!-- dialog -->
<AdaptAutomation automationId="auto-123" forkDisplay={{ mode: 'dialog' }} />
Events
Listen via addEventListener on the underlying <adapt-automation> element:
<AdaptAutomation automationId="auto-123" style="height: 600px;" />
<script>
const el = document.querySelector('adapt-automation');
el?.addEventListener('adapt-session', (e) => console.log(e.detail));
el?.addEventListener('adapt-output', (e) => console.log(e.detail));
</script>
2. AdaptCap
Standalone proof-of-work challenge widget. Use when you manage the automation client yourself.
Since Astro components are server-rendered, you must set the client property via JavaScript after the element is in the DOM:
---
import AdaptCap from '@mochabug/adapt-astro/AdaptCap.astro';
---
<AdaptCap automationId="YOUR_ID" />
<script>
import { createConnectClient, AdaptCapElement } from '@mochabug/adapt-astro/cap';
void AdaptCapElement; // ensure custom element is registered
const el = document.querySelector<AdaptCapElement>('adapt-cap');
if (el) {
el.client = createConnectClient({ id: 'YOUR_ID' });
el.addEventListener('adapt-cap-solve', (e) => {
const { token, expires } = (e as CustomEvent).detail;
console.log('Solved:', token, expires);
});
el.addEventListener('adapt-cap-error', (e) => {
console.error((e as CustomEvent).detail.error);
});
}
</script>
Props
| Prop | Type |
|---|---|
automationId |
string (required) |
workerCount |
number |
i18n |
CapWidgetI18n |
darkMode |
boolean |
class |
string |
style |
string |
JS-only properties
| Property | Type |
|---|---|
client |
AutomationClient — required, set via JS |
i18n |
CapWidgetI18n — label overrides |
Events
| Event | Detail |
|---|---|
adapt-cap-solve |
{ token: string, expires: Date } |
adapt-cap-error |
{ error: Error } |
Headless (no UI)
Use the lower-level API to create and redeem challenges yourself:
import { createChallenge, redeemChallenge, createConnectClient } from '@mochabug/adapt-astro/cap';
const client = createConnectClient({ id: 'YOUR_ID' });
const challenge = await createChallenge(client);
// ... solve with Cap.js or your own solver ...
const redeemed = await redeemChallenge(client, solutions);
Styling
There are three ways to style the Adapt component, from simplest to most powerful.
1. theme prop (recommended)
Pass an AdaptTheme object for semantic token-based theming. Define the theme in frontmatter and pass it as a prop:
---
import AdaptAutomation from '@mochabug/adapt-astro/AdaptAutomation.astro';
import type { AdaptTheme } from '@mochabug/adapt-astro';
const myTheme: AdaptTheme = {
mode: 'light',
primary: '#4f46e5',
background: '#ffffff',
surface: '#f0f4ff',
text: '#1e293b',
textSecondary: '#64748b',
border: '#cbd5e1',
font: '"Inter", sans-serif',
};
---
<AdaptAutomation automationId="auto-123" theme={myTheme} style="height: 600px;" />
AdaptTheme tokens
| Token | Type | Description |
|---|---|---|
mode |
'light' | 'dark' |
Controls dark mode class and shadow/border defaults |
primary |
string |
Accent color -- derives separator, drop target, status icon, and spinner colors |
background |
string |
Main background (panels, active tabs, status cards) |
surface |
string |
Surface color (toolbar, inactive tabs) |
text |
string |
Primary text color (tabs, status, cap widget) |
textSecondary |
string |
Secondary/muted text (inactive tabs) |
border |
string |
Border/separator color (tabs, panels, cap widget, status cards) |
font |
string |
Font family for all panel UI text |
vars |
Record<string, string> |
Direct CSS variable overrides (see below) |
vars escape hatch
For any variable not covered by the semantic tokens, use vars with variable names without the --mb-adapt- prefix. These override everything, including derived values:
---
const myTheme: AdaptTheme = {
mode: 'dark',
primary: '#818cf8',
background: '#0f172a',
surface: '#1e293b',
text: '#f1f5f9',
border: '#334155',
vars: {
'fork-tab-active-bg': '#0f172a',
'floating-radius': '12px',
'cap-spinner-color': '#a5b4fc',
},
};
---
<AdaptAutomation automationId="auto-123" theme={myTheme} style="height: 600px;" />
2. CSS custom properties
Override --mb-adapt-* variables on .mb-adapt using <style is:global> in your layout or page, or import a CSS file in frontmatter.
Inline in a layout or page:
<style is:global>
.mb-adapt {
--mb-adapt-fork-bg: #ffffff;
--mb-adapt-fork-tab-bg: #f5f5f5;
--mb-adapt-fork-tab-active-bg: #ffffff;
--mb-adapt-fork-tab-color: #1a1a1a;
--mb-adapt-fork-tab-inactive-color: #888;
--mb-adapt-fork-separator: #e0e0e0;
--mb-adapt-font: "Inter", sans-serif;
}
.mb-adapt--dark {
--mb-adapt-fork-bg: #1e1e1e;
--mb-adapt-fork-tab-bg: #2a2a2a;
--mb-adapt-fork-tab-active-bg: #1e1e1e;
--mb-adapt-fork-tab-color: #e0e0e0;
--mb-adapt-fork-tab-inactive-color: #777;
--mb-adapt-fork-separator: #3a3a3a;
}
</style>
Or import a CSS file in frontmatter:
---
import '../styles/adapt-theme.css';
---
/* src/styles/adapt-theme.css */
.mb-adapt {
--mb-adapt-fork-bg: #ffffff;
--mb-adapt-fork-tab-bg: #f0f4ff;
--mb-adapt-fork-tab-active-bg: #ffffff;
--mb-adapt-fork-tab-color: #1e293b;
--mb-adapt-fork-tab-inactive-color: #64748b;
--mb-adapt-fork-separator: #cbd5e1;
--mb-adapt-separator-active: rgba(79, 70, 229, 0.5);
--mb-adapt-cap-background: #ffffff;
--mb-adapt-cap-border-color: #e2e8f0;
--mb-adapt-cap-color: #1e293b;
--mb-adapt-cap-spinner-color: #4f46e5;
--mb-adapt-status-card-bg: #ffffff;
--mb-adapt-status-card-border: #e2e8f0;
--mb-adapt-status-text: #334155;
}
.mb-adapt--dark {
--mb-adapt-fork-bg: #0f172a;
--mb-adapt-fork-tab-bg: #1e293b;
--mb-adapt-fork-tab-active-bg: #0f172a;
--mb-adapt-fork-tab-color: #f1f5f9;
--mb-adapt-fork-tab-inactive-color: #94a3b8;
--mb-adapt-fork-separator: #334155;
--mb-adapt-separator-active: rgba(129, 140, 248, 0.6);
--mb-adapt-cap-background: #1e293b;
--mb-adapt-cap-border-color: #334155;
--mb-adapt-cap-color: #f1f5f9;
--mb-adapt-cap-spinner-color: #818cf8;
--mb-adapt-status-card-bg: #1e293b;
--mb-adapt-status-card-border: #334155;
--mb-adapt-status-text: #e2e8f0;
}
3. Direct CSS on internal classes
There is no Shadow DOM -- all classes are in the light DOM, so you can target internal elements directly with <style is:global>.
Animated gradient toolbar:
<style is:global>
@keyframes mb-gradient-shift {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
.mb-group-header {
background: linear-gradient(135deg, #667eea, #764ba2, #f093fb, #667eea);
background-size: 300% 300%;
animation: mb-gradient-shift 6s ease infinite;
}
.mb-group-header .mb-tab[data-active="true"] {
background: rgba(255, 255, 255, 0.25);
color: #ffffff;
}
.mb-group-header .mb-tab {
color: rgba(255, 255, 255, 0.7);
}
/* Dark mode variant */
.mb-adapt--dark .mb-group-header {
background: linear-gradient(135deg, #1e1b4b, #312e81, #4c1d95, #1e1b4b);
background-size: 300% 300%;
animation: mb-gradient-shift 6s ease infinite;
}
.mb-adapt--dark .mb-group-header .mb-tab[data-active="true"] {
background: rgba(255, 255, 255, 0.15);
color: #e0e7ff;
}
.mb-adapt--dark .mb-group-header .mb-tab {
color: rgba(224, 231, 255, 0.6);
}
</style>
Key internal classes
| Class | Element |
|---|---|
.mb-adapt |
Root container |
.mb-adapt--dark |
Dark mode modifier on root |
.mb-group-header |
Toolbar / tab bar |
.mb-tab |
Individual tab |
.mb-tab[data-active="true"] |
Active tab |
.mb-tab-label |
Tab label text |
.mb-group-content |
Panel content area |
.mb-group-header-actions |
Toolbar action buttons (close, pop-out) |
.mb-layout-separator |
Resize handle between panels |
.mb-split-leaf |
Individual panel leaf in a split layout |
Tip: The
classprop styles the outer<adapt-automation>host element. UseclassNamesto add classes to the internal.mb-adaptstructure.
Full CSS variable reference
General
| Variable | Light default | Dark default | Description |
|---|---|---|---|
--mb-adapt-bg |
transparent |
Root & group backgrounds | |
--mb-adapt-font |
system-ui, -apple-system, sans-serif |
All panel UI text | |
--mb-adapt-button-hover-bg |
rgba(128,128,128,0.2) |
rgba(128,128,128,0.3) |
Close/popout/action button hover |
--mb-adapt-separator-active |
rgba(59,130,246,0.5) |
rgba(99,130,246,0.6) |
Resize handle hover/active |
--mb-adapt-border-radius |
8px |
Iframe border radius |
Toolbar and tabs
| Variable | Light default | Dark default | Description |
|---|---|---|---|
--mb-adapt-fork-bg |
#ffffff |
#1e1e1e |
Panel content background |
--mb-adapt-fork-tab-bg |
#f3f3f3 |
#252526 |
Toolbar / inactive tab bg |
--mb-adapt-fork-tab-active-bg |
#ffffff |
#1e1e1e |
Active tab background |
--mb-adapt-fork-tab-color |
rgb(51,51,51) |
#ffffff |
Active tab text |
--mb-adapt-fork-tab-inactive-color |
rgba(51,51,51,0.7) |
#969696 |
Inactive tab text |
--mb-adapt-fork-separator |
rgba(128,128,128,0.35) |
rgb(68,68,68) |
Tab/panel borders |
--mb-adapt-tab-radius |
0 |
Tab border-radius (use 999px for pill shape) |
|
--mb-adapt-tab-shadow |
none |
Tab box-shadow | |
--mb-adapt-tab-active-shadow |
none |
Active tab box-shadow | |
--mb-adapt-tab-gap |
0px |
Tab margin (spacing between tabs) | |
--mb-adapt-tab-padding |
0 14px |
Tab padding | |
--mb-adapt-tab-font-size |
13px |
Tab label font size | |
--mb-adapt-toolbar-height |
40px |
Toolbar / tab bar height | |
--mb-adapt-toolbar-padding |
0 |
Toolbar inner padding (standard CSS shorthand) | |
--mb-adapt-tab-min-width |
100px |
Tab minimum width | |
--mb-adapt-tab-spacing |
6px |
Gap between tab label and action buttons |
Floating panels (elevation)
| Variable | Light default | Dark default | Description |
|---|---|---|---|
--mb-adapt-floating-shadow |
0 25px 50px -12px rgba(0,0,0,0.25), 0 12px 24px -8px rgba(0,0,0,0.15) |
... rgba(0,0,0,0.5), ... rgba(0,0,0,0.3) |
Overlay box-shadow |
--mb-adapt-floating-border |
none |
1px solid rgba(255,255,255,0.06) |
Overlay border |
--mb-adapt-floating-backdrop |
none |
Overlay backdrop-filter | |
--mb-adapt-floating-radius |
8px |
Overlay border-radius | |
--mb-adapt-status-card-shadow |
0 4px 24px rgba(0,0,0,0.08), 0 2px 8px rgba(0,0,0,0.04) |
... rgba(0,0,0,0.25), ... rgba(0,0,0,0.15) |
Status card box-shadow |
--mb-adapt-drag-ghost-shadow |
0 4px 12px rgba(0,0,0,0.15) |
0 4px 12px rgba(0,0,0,0.35) |
Drag ghost box-shadow |
Drop targets
| Variable | Light default | Dark default |
|---|---|---|
--mb-adapt-drop-header-bg |
rgba(99,102,241,0.18) |
rgba(129,140,248,0.22) |
--mb-adapt-drop-center-bg |
rgba(99,102,241,0.12) |
rgba(129,140,248,0.15) |
--mb-adapt-drop-split-bg |
rgba(99,102,241,0.14) |
rgba(129,140,248,0.18) |
--mb-adapt-drop-border |
rgba(99,102,241,0.55) |
rgba(129,140,248,0.6) |
Status cards
| Variable | Light default | Dark default |
|---|---|---|
--mb-adapt-status-card-bg |
#ffffff |
#1e293b |
--mb-adapt-status-card-border |
#e5e7eb |
#334155 |
--mb-adapt-status-icon-bg |
#fef2f2 |
#351c1c |
--mb-adapt-status-text |
#374151 |
#e2e8f0 |
Cap widget
| Variable | Light default | Dark default |
|---|---|---|
--mb-adapt-cap-background |
#ffffff |
#1e293b |
--mb-adapt-cap-border-color |
#e2e8f0 |
#334155 |
--mb-adapt-cap-border-radius |
16px |
|
--mb-adapt-cap-height |
72px |
|
--mb-adapt-cap-width |
380px |
|
--mb-adapt-cap-padding |
20px 28px |
|
--mb-adapt-cap-gap |
20px |
|
--mb-adapt-cap-color |
#1e293b |
#f1f5f9 |
--mb-adapt-cap-checkbox-size |
36px |
|
--mb-adapt-cap-checkbox-border |
2px solid #cbd5e1 |
2px solid #475569 |
--mb-adapt-cap-checkbox-radius |
10px |
|
--mb-adapt-cap-checkbox-background |
#f8fafc |
#0f172a |
--mb-adapt-cap-spinner-color |
#6366f1 |
#818cf8 |
--mb-adapt-cap-spinner-bg |
#e2e8f0 |
#334155 |
--mb-adapt-cap-spinner-thickness |
3px |
|
--mb-adapt-cap-font |
inherit |
Z-index / stacking
| Variable | Default | Description |
|---|---|---|
--mb-adapt-z-base |
0 |
Base z-index offset — added to all internal z-index values |
Set --mb-adapt-z-base to shift all internal z-index values. Useful when embedding inside modals or drawers that have their own stacking context. Example: --mb-adapt-z-base: 10000 lifts all layers by 10000.
Internal stacking order from low to high: separators (1), resize handles (10), minimized tabs (100), floating panels (100000+), status/cap (200000), confirm dialog (300000), drop targets (999998), drag ghost (999999). All values are offset by --mb-adapt-z-base.
Props
| Prop | Type |
|---|---|
automationId |
string (required) |
sessionToken |
string |
authToken |
string |
transmitter |
string |
signals |
{ [key: string]: SignalValue } |
challengeToken |
string |
requiresChallenge |
boolean |
capWidgetOptions |
{ workerCount?: number; i18n?: CapWidgetI18n } |
inheritToken |
string |
inheritFrom |
{ hash: string } | { param: string } |
forkDisplay |
{ mode: 'side-by-side', split?: number } | { mode: 'dialog' } |
darkMode |
boolean |
autoResizing |
boolean |
allowFloating |
boolean — hide pop-out buttons and block user-initiated floating (default true) |
allowDocking |
boolean — hide dock buttons and block user-initiated docking (default true) |
allowDialogDocking |
boolean — allow tab splits inside floating dialog overlays (default true) |
floatingAutoResize |
boolean — floating overlays auto-resize from iframe content (default false) |
confirmOnClose |
boolean — show confirmation dialog before leaving page (default false) |
persist |
boolean | PersistOptions |
text |
StatusText |
theme |
AdaptTheme |
classNames |
{ root?: string; iframe?: string; statusMessage?: string; statusCard?: string } |
class |
string |
style |
string |
License
ISC (c) mochabug AB