diff --git a/docs/ai-docs.md b/docs/ai-docs.md
index f81ce62..bfed83f 100644
--- a/docs/ai-docs.md
+++ b/docs/ai-docs.md
@@ -33,6 +33,9 @@ plugins:
| `llms_config` | `string` | `llms_config.json` | Path to the LLM config file, relative to `mkdocs.yml`. |
| `ai_resources_page` | `bool` | `true` | Generate the AI resources page from `ai-resources.md`. Set to `false` to disable. |
| `ai_page_actions` | `bool` | `true` | Inject the per-page AI actions widget next to each H1. Set to `false` to disable. |
+| `ai_page_actions_anchor` | `string` | `""` | CSS class name of the element(s) to append the widget into instead of wrapping the H1. When set, the default H1-wrapping behavior is replaced — see [Custom anchor](#custom-anchor). |
+| `ai_page_actions_style` | `string` | `"split"` | Widget layout style. `"split"` renders a copy button left of the dropdown arrow; `"dropdown"` renders a single labelled button with all actions inside — see [Widget style](#widget-style). |
+| `ai_page_actions_dropdown_label` | `string` | `"Markdown for LLMs"` | Trigger button label when `ai_page_actions_style` is `"dropdown"`. |
| `enabled` | `bool` | `true` | Disable the entire plugin (all features). Supports `!ENV` for environment-based toggling. |
The `llms_config` file controls content filtering, category definitions, and output paths. See [Resolve Markdown](resolve-md.md#-configuration) for a full breakdown of the `llms_config.json` schema.
@@ -52,10 +55,56 @@ Always runs when the plugin is enabled. Processes every documentation markdown f
### AI page actions (`ai_page_actions`)
-Injects a split-button dropdown widget next to each page's H1 heading at build time. The widget lets readers copy, download, or open the page's resolved markdown in an LLM tool. Pages listed in `llms_config.json` exclusions, dot-directories, and pages with `hide_ai_actions: true` in their front matter are automatically skipped.
+Injects an AI actions widget (split-button by default, or plain dropdown via `ai_page_actions_style`) next to each page's H1 heading at build time. The widget lets readers copy, download, or open the page's resolved markdown in an LLM tool. Pages listed in `llms_config.json` exclusions, dot-directories, and pages with `hide_ai_actions: true` in their front matter are automatically skipped.
See [AI Page Actions](ai-page-actions.md) for details on exclusion rules, toggle page handling, and styling.
+#### Widget style
+
+The widget supports two layout styles, controlled by `ai_page_actions_style`.
+
+**`split`** (default) — a copy button sits to the left of a chevron trigger that opens the dropdown:
+
+```yaml
+plugins:
+ - ai_docs:
+ ai_page_actions_style: split # default, same as omitting the option
+```
+
+**`dropdown`** — a single labelled button opens a menu that contains all actions, including copy. No separate copy button is rendered outside the menu:
+
+```yaml
+plugins:
+ - ai_docs:
+ ai_page_actions_style: dropdown
+ ai_page_actions_dropdown_label: Markdown for LLMs # default
+```
+
+The container gets the additional CSS class `ai-file-actions-container--dropdown` so you can style the two modes independently. Resources table widgets always carry `ai-file-actions-container--table`. See [Styling](ai-page-actions.md#styling) for the full class reference and CSS examples.
+
+#### Custom anchor
+
+By default, the widget is placed by wrapping the H1 in a `
` flex container. If your theme or custom layout already has a dedicated slot for page-level actions, you can redirect the widget there instead:
+
+```yaml
+plugins:
+ - ai_docs:
+ ai_page_actions_anchor: my-page-actions
+```
+
+The plugin then finds every element that has the class `my-page-actions` within `.md-content` and appends the widget into it, leaving the H1 untouched. If no matching element is found on a given page, the page is left unchanged and a debug message is logged.
+
+#### Toggle pages
+
+When using `ai_page_actions_anchor` alongside the [`page_toggle` plugin](page-toggle.md), your template must render one anchor element per variant inside the toggle container, each carrying the matching `data-variant` attribute. For example:
+
+```html
+
+
+```
+
+A Jinja macro is a convenient way to do this — iterate over your variants and emit the element with the correct `data-variant` for each one.
+
### AI resources page (`ai_resources_page`)
Automatically generates the content for a page named `ai-resources.md`, replacing it with a table listing all available LLM artifact files (global indexes and per-category bundles) with copy, view, and download actions.
diff --git a/docs/ai-page-actions.md b/docs/ai-page-actions.md
index 1ba6fdc..512b859 100644
--- a/docs/ai-page-actions.md
+++ b/docs/ai-page-actions.md
@@ -50,6 +50,32 @@ The plugin loads `llms_config.json` from the project root (the directory contain
The widget uses the same CSS classes as the table widget (`ai-file-actions.css`). The H1 wrapper layout is controlled by `.h1-ai-actions-wrapper`, which includes mobile responsive styles that stack the H1 and widget vertically on small screens.
+### CSS classes
+
+Every widget shares the base container class. Modifier classes are added automatically based on context, giving you clean CSS selectors for each placement:
+
+| Class | Present on |
+| :--- | :--- |
+| `.ai-file-actions-container` | Every widget (base class) |
+| `.ai-file-actions-container--dropdown` | Per-page widget rendered in dropdown style (applied by `ai_docs` when `ai_page_actions_style: dropdown`) |
+| `.ai-file-actions-container--table` | Widgets inside the AI resources page tables |
+
+### Targeting each widget independently
+
+```css
+/* All widgets */
+.ai-file-actions-container { }
+
+/* Per-page split style only (default) */
+.ai-file-actions-container:not(.ai-file-actions-container--dropdown):not(.ai-file-actions-container--table) { }
+
+/* Per-page dropdown style only (when used via ai_docs with ai_page_actions_style: dropdown) */
+.ai-file-actions-container--dropdown { }
+
+/* Resources table widgets only */
+.ai-file-actions-container--table { }
+```
+
## Client-Side Behavior
The widget relies on `ai-file-actions.js` for all client-side interactions (copy, download, dropdown toggle, keyboard navigation, analytics). No additional JavaScript is needed.
diff --git a/helper_lib/ai_file_utils/ai_file_utils.py b/helper_lib/ai_file_utils/ai_file_utils.py
index df7c5d0..65d8248 100644
--- a/helper_lib/ai_file_utils/ai_file_utils.py
+++ b/helper_lib/ai_file_utils/ai_file_utils.py
@@ -359,14 +359,25 @@ def generate_dropdown_html(
site_url: str = "",
label_replace: dict[str, str] | None = None,
content: str = "",
+ style: str = "split",
+ dropdown_label: str = "Markdown for LLMs",
+ extra_classes: str = "",
) -> str:
"""
- Generate the HTML for the AI file actions split-button.
+ Generate the HTML for the AI file actions widget.
- The action marked ``primary: true`` in the JSON renders
- as the left-side button; all other actions render as
- dropdown items. The primary action is automatically
- excluded from the dropdown.
+ Two styles are supported:
+
+ ``"split"`` (default)
+ The action marked ``primary: true`` in the JSON renders as a
+ left-side button; all other actions render as dropdown items.
+ The primary action is automatically excluded from the dropdown.
+
+ ``"dropdown"``
+ A single trigger button labelled ``dropdown_label`` (default
+ ``"Markdown for LLMs"``) opens a menu that contains *all*
+ actions, including the primary one. No separate copy button
+ is rendered.
Args:
url: The relative URL of the file to act upon.
@@ -375,12 +386,18 @@ def generate_dropdown_html(
from the dropdown.
primary_label: Optional label override for the primary
button (e.g., "Copy page" vs default "Copy file").
+ Only used in ``"split"`` style.
site_url: The base site URL (e.g., "https://docs.polkadot.com/").
When provided, ``page_url`` passed to prompt templates
will be the fully-qualified URL.
label_replace: Optional dict of string replacements to apply
to dropdown item labels (e.g., ``{"file": "page"}``).
content: Optional page content for prompt template interpolation.
+ style: Widget style — ``"split"`` or ``"dropdown"``.
+ dropdown_label: Trigger button label used in ``"dropdown"`` style.
+ extra_classes: Additional CSS class(es) to append to the container
+ div (e.g., ``"ai-file-actions-container--table"``).
+ Multiple classes can be space-separated.
Returns:
The HTML string for the component.
@@ -396,10 +413,83 @@ def generate_dropdown_html(
page_url=url, filename=filename, content=content, prompt_page_url=full_url
)
- # Separate primary action from dropdown actions
+ exclude_set = set(exclude) if exclude else set()
+
+ # Build container class string — style modifier is auto-added, then
+ # any caller-supplied extra_classes are appended on top.
+ container_classes = "ai-file-actions-container"
+ if style == "dropdown":
+ container_classes += " ai-file-actions-container--dropdown"
+ if extra_classes:
+ container_classes += f" {extra_classes}"
+
+ chevron = (
+ ''
+ )
+
+ # ------------------------------------------------------------------ #
+ # "dropdown" style — single trigger button, all actions in the menu #
+ # ------------------------------------------------------------------ #
+ if style == "dropdown":
+ menu_items = ""
+ for action in actions:
+ if action.get("id") in exclude_set:
+ continue
+ if label_replace and "label" in action:
+ for old, new in label_replace.items():
+ action["label"] = action["label"].replace(old, new)
+ menu_items += self._render_action_item(action, url)
+
+ safe_url = html.escape(url, quote=True)
+ escaped_label = html.escape(dropdown_label, quote=True)
+ markdown_icon = (
+ ''
+ )
+ trigger_btn = (
+ '"
+ )
+ dropdown_menu = (
+ '