@desource/context7-widget
v0.2.0
Published
Customizable Context7 documentation chat widget with TypeScript helpers and a drop-in script build.
Maintainers
Readme
@desource/context7-widget
Style Context7 docs chat to fit your website, with a script tag or TypeScript. This MIT-licensed package adds the control a carefully designed site needs: fonts, colors, spacing, custom buttons, and flexible panel placement.
Why It Exists
Context7 provides its docs widget and hosted AI answers for free. The official widget’s basic visual options leave little room to match your design. We built this version so the assistant can look and feel like part of your product.
Keep Context7’s search and AI answers. Add your own styling, multiline questions, copyable answers and code, Stop and Retry controls, and scrolling that respects what the visitor is reading. Existing widget users can migrate by changing the script URL; their library and allowed-domain settings carry over.
New to Context7? Give visitors a place to ask about your library, find an example, and take the first step toward using it. This project builds on Context7’s free docs chat initiative with an interface you can make your own, also free.
Use the root entry for the custom element and mounting helpers, /core for
custom chat interfaces, or /kit to build native framework integrations. ESM
imports are tree-shakeable and do not mount anything; only /widget.js starts
itself.
Before You Start
Use a library you have claimed on Context7. In its Admin → Chat settings,
enable the widget, add your site's domain to the allowed domains, and save.
Replace /owner/repo in the examples with that library's id. A library being
indexed alone does not enable chat on your site.
See Context7's widget setup.
Install
npm install @desource/context7-widgetDrop-In Browser Script
No package manager or build step is required:
<script
async
src="https://context7.desourcelabs.com/widget.js"
data-library="/owner/repo"
data-position="anchor"
data-preset="glass"
></script>The hosted file is a classic, self-starting IIFE. Multiple script tags are
supported when a page needs multiple independently configured widgets; give
each one a unique data-widget-id.
Mount A Widget
import { mountContext7Widget } from '@desource/context7-widget';
const widget = mountContext7Widget({
library: '/owner/repo',
position: 'center',
preset: 'glass',
theme: 'auto',
backdrop: true,
closeOnOutsideClick: true
});
widget.open();
await widget.send('Show me the recommended setup.');
widget.cancel();
await widget.retry();
widget.reset();Context7WidgetElement exposes open, close, toggle, send, cancel,
retry, reset, isOpen, isBusy, and getMessages. The same operations are
available by widgetId through window.Context7Widget.
Imports are SSR-safe. Element creation, mounting, and imperative DOM operations still require a browser document and fail with a focused error when called on the server.
Direct Custom Element
Register the element once when declarative markup fits your application better:
import { defineContext7Widget } from '@desource/context7-widget';
defineContext7Widget();<context7-widget library="/owner/repo" position="center" preset="minimal" backdrop="true"></context7-widget>Custom tag names are supported without reusing the same registered constructor:
defineContext7Widget('context7-docs-widget');Examples
Mount an anchored widget beside an existing help button:
<button id="docs-help">Ask docs</button>import { mountContext7Widget } from '@desource/context7-widget';
const widget = mountContext7Widget({
library: '/owner/repo',
customTrigger: '#docs-help',
position: 'anchor',
preset: 'minimal',
widgetId: 'docs'
});
await widget.send('Show the installation steps.');Use position: 'center' with backdrop: true for a modal help flow. Use a
corner position without customTrigger for the built-in floating launcher.
Runnable framework-neutral examples are available in the
demo gallery.
Options
JavaScript uses camel-case option names. Direct custom-element attributes use
kebab case; script installs prefix those attributes with data-.
| JavaScript option | Custom-element attribute | Type / default |
| --------------------- | ------------------------ | ------------------------------------------------------------------ |
| library | library | Required Context7 library id |
| position | position | bottom-right; also corners, center, or anchor |
| preset | preset | default; also minimal, glass, neo, terminal, brutalist |
| theme | theme | auto; also light or dark |
| color | color | No override; presets own the accent |
| customTrigger | custom-trigger | CSS selector, simple element id, or Element in JavaScript |
| backdrop | backdrop | true for center, otherwise false |
| closeOnOutsideClick | close-on-outside-click | true |
| defaultOpen | default-open | false |
| initialMessage | initial-message | Built-in greeting; {library} is interpolated |
| labels | Property only | Partial localization object merged with English defaults |
| launcherLabel | launcher-label | Ask Docs AI |
| launcherVariant | launcher-variant | icon; also pill or badge |
| linkBaseUrl | link-base-url | Base for relative links; defaults to the Context7 library page |
| panelHeight | panel-height | Responsive stylesheet default |
| panelWidth | panel-width | Responsive stylesheet default |
| placeholder | placeholder | Ask about the docs... |
| title | dialog-title | Chat with Documentation |
| widgetId | widget-id | default |
For script tags, library becomes data-library, dialog-title becomes
data-title, and the remaining attributes follow the same data-* pattern.
The custom element intentionally uses dialog-title rather than the native
HTML title attribute, avoiding an accidental browser tooltip.
Boolean attributes accept explicit values such as backdrop="false" and
close-on-outside-click="false"; their mere presence does not force them to
true.
For a mounted element, assign widget.labels = { send: 'Enviar', ... } to
localize visible and assistive text, including attribution and missing-library
fallbacks. JavaScript helpers accept the same
labels option. Script-tag data-* values remain live after connection; a
canonical custom-element attribute takes precedence only when the application
explicitly authors it.
Generate A Script Tag
import { buildContext7WidgetScriptTag } from '@desource/context7-widget';
const script = buildContext7WidgetScriptTag({
library: '/owner/repo',
customTrigger: '#docs-chat',
position: 'anchor',
preset: 'minimal'
});Selector custom triggers hide the built-in launcher only after a matching element binds. Missing or late-rendered selectors keep the launcher available and bind automatically when the target appears.
The script still sends chat requests to https://context7.com. This package
does not run a Context7 proxy; it supplies the customizable client layer.
Data Flow
The browser posts the configured library id and current conversation messages
directly to https://context7.com/api/v2/widget/chat. DeSource Labs does not
proxy chat content. This client adds no analytics, cookies, or persistent
browser storage; state remains in the live widget's memory and reset() clears
it. Public events expose questions and answers to the host application, so
integrators control any additional analytics or persistence. Avoid sending
secrets or sensitive personal data and review Context7's policies for backend
processing and retention.
Supported Visual Modes
- Positions:
bottom-right,bottom-left,top-right,top-left,center, andanchor - Presets:
default,minimal,glass,neo,terminal, andbrutalist - Themes:
light,dark, andauto - Launcher variants:
icon,pill, andbadge
If color is omitted, the preset owns the launcher and send-button color. Set
color only when your product needs a brand override.
Customization
Choose a preset first, then override public tokens or parts. Internal .c7-*
classes are private and can change between releases.
Styling Contract
Style the custom element from the host page. Do not target internal .c7-*
classes.
context7-widget[widget-id='docs'] {
--c7-accent: #7cffb2;
--c7-accent-contrast: #07120c;
--c7-font-family: Inter, ui-sans-serif, system-ui, sans-serif;
--c7-panel-background: #101513;
--c7-panel-color: #f7f2e8;
--c7-border-color: rgba(247, 242, 232, 0.18);
--c7-panel-radius: 8px;
}
context7-widget::part(send-button) {
min-width: 5rem;
text-transform: uppercase;
}Public variables include:
- Brand/type:
--c7-accent,--c7-accent-contrast,--c7-font-family,--c7-muted-color,--c7-focus-ring - Panel:
--c7-panel-background,--c7-panel-backdrop-filter,--c7-panel-color,--c7-panel-width,--c7-panel-height,--c7-panel-radius,--c7-panel-shadow,--c7-border-color,--c7-spacing,--c7-z-index - Launcher:
--c7-launcher-background,--c7-launcher-color,--c7-launcher-gap,--c7-launcher-radius,--c7-launcher-shadow,--c7-launcher-size - Backdrop:
--c7-backdrop,--c7-backdrop-filter - Header/footer:
--c7-header-background,--c7-footer-background - Messages:
--c7-message-assistant-background,--c7-message-assistant-color,--c7-message-user-background,--c7-message-user-color,--c7-message-radius,--c7-error-background,--c7-error-color - Controls:
--c7-control-background,--c7-control-border,--c7-control-color
Stable shadow parts:
backdrop, panel, header, title, close-button, messages, message,
assistant-message, user-message, error-message, typing, tool-call,
tool-toggle, code-block, composer, input, send-button, footer,
powered-by, and launcher.
Events
Listen on the element or at document level:
import type { Context7WidgetQuestionEventDetail } from '@desource/context7-widget';
document.addEventListener('c7:answer-complete', (event) => {
// `event.detail` is inferred from the event name.
console.log(event.detail.library, event.detail.answer);
});
function trackQuestion(detail: Context7WidgetQuestionEventDetail) {
console.log(detail.question);
}Events: c7:ready, c7:open, c7:close, c7:cancel, c7:question,
c7:first-token, c7:answer, c7:answer-complete, c7:tool-call,
c7:tool-result, and c7:error.
Cancelling after answer tokens arrive preserves the visible partial assistant
message in getMessages() with status: 'cancelled'. send() resolves with a
status result such as complete, cancelled, error, busy, or empty.
retry() resends the last failed question without duplicating the user message.
The composer is multiline: Enter sends and Shift+Enter inserts a newline. Answers and fenced code blocks have copy actions. Markdown escapes raw HTML, allows safe HTTP(S) and relative links, and supports headings, fenced code with lightweight highlighting, nested lists, tasks, blockquotes, tables, and inline formatting. Streaming text stays escaped plain text and Markdown parsing is deferred until the answer completes, avoiding quadratic work while long responses stream.
Headless Engine Subscriptions
Context7ConversationEngine.subscribe(listener) includes every state snapshot
by default. Framework or custom renderers that consume the event stream can use
subscribe(listener, { includeTransient: false }) to skip snapshots caused
only by partial-answer or tool-frame streaming. Request start, committed
messages, cancellation, errors, reset, and final busy state are still delivered;
subscribeEvents continues to emit every stream event.
State and event listeners are invoked independently. If consumer code throws, the error is logged and the engine continues the request and invokes the remaining listeners. Both subscription methods return idempotent unsubscribe callbacks.
Exports
@desource/context7-widget@desource/context7-widget/core— useful framework-neutral primitives for a custom experience: engine, renderer bridge, transport, Markdown, layout, clipboard and copy-action coordination, modal isolation, defaults, contracts, and labels@desource/context7-widget/kit— rendering-independent conversation engine, renderer bridge, transport, markdown, floating-layout calculation, contracts, defaults, and brand assets for framework packages@desource/context7-widget/widget.js
The root, /core, and /kit are ESM-only and preserve internal module
boundaries. Downstream bundlers can therefore omit the custom-element runtime
when an application imports only renderMarkdown or
resolveContext7AnchorLayout from /core. /kit is intentionally a broader
framework-author surface; application integrations should prefer the root or
/core. The widget.js subpath is the classic browser script and intentionally
has side effects.
The custom element shares one constructable stylesheet across instances when the browser supports it and falls back to an inline shadow stylesheet otherwise. Streamed DOM updates are frame-throttled, while answer events remain available for every received chunk.
The package publishes ESM only. Its root, /core, and /kit declarations are
validated for modern Node ESM and TypeScript bundler resolution.
Browser, Accessibility, And Security Notes
The runtime targets ES2020-era modern browsers with Custom Elements, open
Shadow DOM, fetch, ReadableStream, and AbortController. Constructable
stylesheets are shared when available; older browsers receive an inline
<style> fallback. Centered panels use modal dialog semantics, keep keyboard
focus inside the panel, make outside content inert, lock page scrolling, close
with Escape, and restore both document state and focus to the opener.
Corner and anchored panels remain non-modal.
For a restrictive Content Security Policy, allow:
- the widget origin in
script-src; https://context7.cominconnect-src;data:inimg-srcfor the embedded DeSource Labs mark.
Browsers using the inline stylesheet fallback also need a compatible
style-src policy. Test the final policy in every browser your application
supports.
Related packages
@desource/context7-widget-vueprovides a native Vue 3 component, composable, plugin defaults, and custom trigger slots.@desource/context7-widget-nuxtadds auto-imports, automatic styles, and app-wide defaults for Nuxt 3 and 4.@desource/context7-widget-reactprovides a native React component, controlled open state, a programmatic hook, and custom triggers.@desource/context7-widget-svelteprovides a native Svelte 5 component, bindable open state, trigger snippets, and a reactive controller.@desource/context7-widget-angularprovides a standalone Angular component, signals, application defaults, and injectable controls.
