diff --git a/widget/README.md b/widget/README.md
index 2e3c3b4580..2a5c640191 100644
--- a/widget/README.md
+++ b/widget/README.md
@@ -123,6 +123,7 @@ That's it! The widget will appear as a floating button in the bottom-right corne
| `text-color` | string | `#000000bf` | Text color (75% opacity black) |
| `mode` | string | `"launcher"` | Display mode: `"launcher"` or `"inline"` |
| `include-citations`| boolean | `false` | Include citation markers in responses |
+| `start-expanded` | boolean | `false` | Inline mode: show the full chat panel before the first message, instead of the compact input bar |
**Note**: These attributes must be provided as HTML attributes. Only `backend-url` and `api-key` can optionally be set via environment variables for self-hosted builds.
@@ -232,7 +233,21 @@ The widget is embedded directly in your page layout. Perfect for dedicated suppo
```
-**CSS Tip**: The widget will fill its container's dimensions in inline mode.
+**CSS Tip**: The widget will fill its container's dimensions in inline mode. Give the container an explicit height; otherwise the panel grows with the conversation.
+
+Before the first message, inline mode shows a compact input bar. Add `start-expanded` to show the full chat panel at the container's size from the start:
+
+```html
+
+
+
+```
+
+## Keyboard Events
+
+Key presses typed into the widget do not propagate to the host page (except Escape). The widget uses Shadow DOM, so a page-level handler sees `event.target` as ``, not the text field. Without this, handlers that block Backspace outside text fields would also block it in the widget.
+
+Handlers registered in the capture phase still run first. If such a handler blocks keys outside text fields, check `event.composedPath()[0]` instead of `event.target`.
## Development
diff --git a/widget/src/config/config.ts b/widget/src/config/config.ts
index ee2fa5278e..c098afa0b3 100644
--- a/widget/src/config/config.ts
+++ b/widget/src/config/config.ts
@@ -23,6 +23,7 @@ export function resolveConfig(attributes: Partial): WidgetConfig {
logo: attributes.logo,
mode: attributes.mode || "launcher",
includeCitations: attributes.includeCitations ?? false,
+ startExpanded: attributes.startExpanded ?? false,
};
if (!config.backendUrl) {
diff --git a/widget/src/styles/widget-styles.ts b/widget/src/styles/widget-styles.ts
index 6f94118c25..c6483b1272 100644
--- a/widget/src/styles/widget-styles.ts
+++ b/widget/src/styles/widget-styles.ts
@@ -10,6 +10,12 @@ export const widgetStyles = css`
font-family: var(--onyx-font-family);
}
+ /* Without a host height, the inline container's height: 100% resolves to
+ auto and the panel grows with its content instead of filling the parent. */
+ :host([mode="inline"]) {
+ height: 100%;
+ }
+
.launcher {
position: fixed;
background: var(--background-neutral-00);
diff --git a/widget/src/types/widget-types.ts b/widget/src/types/widget-types.ts
index 1ee62b2b70..615973bde5 100644
--- a/widget/src/types/widget-types.ts
+++ b/widget/src/types/widget-types.ts
@@ -29,6 +29,8 @@ export interface WidgetConfig {
// Optional - Display
mode?: "launcher" | "inline";
+ // Inline mode: show the full chat panel before the first message.
+ startExpanded?: boolean;
// Optional - Citations
includeCitations?: boolean;
diff --git a/widget/src/widget.ts b/widget/src/widget.ts
index d0ace958a5..d5dfd619b8 100644
--- a/widget/src/widget.ts
+++ b/widget/src/widget.ts
@@ -35,9 +35,12 @@ export class OnyxChatWidget extends LitElement {
@property({ attribute: "text-color" }) textColor?: string;
@property({ attribute: "agent-name" }) agentName?: string;
@property({ attribute: "logo" }) logo?: string;
- @property() mode?: "launcher" | "inline";
+ // Reflected so `:host([mode="inline"])` also matches when set as a property.
+ @property({ reflect: true }) mode?: "launcher" | "inline";
@property({ attribute: "include-citations", type: Boolean })
includeCitations?: boolean;
+ @property({ attribute: "start-expanded", type: Boolean })
+ startExpanded?: boolean;
// Assigned as a JS property, since a function cannot ride an HTML attribute.
// Takes precedence over `api-key` and keeps the credential out of the markup.
@@ -113,6 +116,7 @@ export class OnyxChatWidget extends LitElement {
logo: this.logo,
mode: this.mode,
includeCitations: this.includeCitations,
+ startExpanded: this.startExpanded,
});
// Apply custom colors
@@ -358,6 +362,13 @@ export class OnyxChatWidget extends LitElement {
}
private handleKeyDown(e: KeyboardEvent) {
+ // Shadow DOM retargets `e.target` to the host element, so page-level
+ // handlers that let keys through only for text fields (e.g. ones that
+ // block Backspace navigation) would cancel typing here. Escape still
+ // propagates so the host page can close its overlays.
+ if (e.key !== "Escape") {
+ e.stopPropagation();
+ }
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
this.sendMessage();
@@ -538,7 +549,10 @@ export class OnyxChatWidget extends LitElement {
render() {
const showContainer = this.config.mode === "inline" || this.isOpen;
const hasMessages = this.messages.length > 0 || this.isStreaming;
- const isCompactInline = this.config.mode === "inline" && !hasMessages;
+ const isCompactInline =
+ this.config.mode === "inline" &&
+ !this.config.startExpanded &&
+ !hasMessages;
return html`
${this.config.mode === "launcher"