Build interactive Power BI reports with plain HTML
Interactive HTML is a free Power BI visual. Write your layout in a DAX measure, add a few attributes, and your cards filter the page, open drawers and switch tabs. You write no JavaScript.
Get StartedTry the demoThis window is a demo. In Power BI, the cards and panel are one Interactive HTML visual, built from a DAX measure.
What you can do with it
If you have used the HTML Content visual, you already know how to start. Interactive HTML shows the same markup, then lets it respond to clicks.
Filter the whole page
Click a card and every other visual responds, just like a bar chart. Ctrl+click selects several.
Open drawers and modals
Show details for the row you clicked. No bookmarks and no duplicate pages.
Switch tabs inside one visual
Build tabs, toggles and density switches with two attributes.
Use Power BI’s own menus
Right-click for Include, Exclude and Show as a table. Hover for native tooltips.
Match your theme
Colours can follow the report theme. Show different content when editing and reading.
Stay safe
Scripts, event handlers and external links are removed. The visual makes no network requests.
Let AI write your measures
You do not need to learn the attributes first. Copy the prompt below into Claude, Copilot, ChatGPT or any AI assistant, describe the card you want in plain English, and paste the measure it gives you into Power BI.
Copy the prompt
It holds the full rules, so the AI writes measures that work the first time.
Describe what you want
Add your table and column names and say what the card should show and do.
Paste into Power BI
Create a new measure, paste the result, and put it in the HTML well.
Things you can ask for
- A KPI card per service showing MTTR against target, red when it is over
- Clicking a card filters the whole page
- A drawer that breaks the number down
- Three tabs: Overview, Trend and Notes
- Sort links for highest and lowest
Example request to send after the prompt: “Using the table Sales with columns Region and Amount, make a card per region that shows total amount, filters the page when clicked, and opens a drawer with the region’s name and amount.”
You write Power BI DAX measures that return HTML for the Interactive HTML visual. Output a complete measure, nothing else. After the measure, add one line starting "Bind:" that says which column to put in the Category well.
## How the visual works
- A DAX measure returns an HTML string. The visual sanitizes and renders it.
- Field wells: HTML (the measure, required), Category (renders the markup once per value, each with its own selection identity), Tooltips (extra values).
- With a Category bound, your markup renders once per category row. Panel state and selection are per row automatically.
- If I do not give table or column names, use clear placeholders such as 'Table'[Column].
## Hard rules
1. DAX strings use double quotes, so write ALL HTML attributes with SINGLE quotes: style='...' not style="...".
2. Concatenate with &. Wrap measure values in FORMAT() to control their text.
3. NEVER emit <script>, on* handlers, javascript: URLs, @import, expression(), or url() pointing anywhere but a data:image URI. They are stripped.
4. All styling is ordinary inline CSS. There is no styling vocabulary.
5. Declare panels (data-pbi-view) as TOP-LEVEL siblings of the card, never nested inside it.
6. drawer/modal panels are relocated in the DOM, so style them inline or by their own class, never with a descendant selector.
7. data-pbi-when elements start hidden until a matching data-pbi-set fires.
8. Escape HTML entities in text: & < > — › ×.
9. Images must be embedded: <img src> and CSS url() take only data:image URIs (png, jpeg, gif, webp, svg+xml, avif). The visual makes no network requests, so an external image URL is removed. srcset is always removed.
## Vocabulary (21 attributes, exhaustive - use nothing else)
SELECTION
data-pbi-select empty | "multi" cross-filter the page on click
data-pbi-clear empty clear the selection
data-pbi-selected empty marker; gets .pbi-is-selected
data-pbi-highlighted empty marker; gets .pbi-is-highlighted
data-pbi-context-menu empty native right-click menu
PANELS
data-pbi-view identifier declare a named panel
data-pbi-as drawer|modal|inline|tab how it presents
data-pbi-click open:id|close:id|toggle:id
data-pbi-group identifier grouped views act as radios
data-pbi-persist empty survives bookmarks/navigation
STATE
data-pbi-set key=value set state on click
data-pbi-when key=value show only while state matches
PRESENTATION
data-pbi-tooltip text, may use {{Field}} tokens
data-pbi-scope live | static static keeps colour when dimmed
data-pbi-theme-color foreground|background|accent1..accent8 sets --pbi-theme on the element
data-pbi-when-mode view | edit | export
data-pbi-empty empty shown when there are no rows
POWER BI ACTIONS
data-pbi-open-url absolute https URL host prompts the viewer first
data-pbi-sort field|field:asc|field:desc client-side reorder
data-pbi-tooltip-page empty report-page tooltip
data-pbi-drill down|up|next BEST-EFFORT; Desktop ignores it. Prefer native drill buttons.
## Grammars
- identifiers (view ids, groups, state keys): ^[A-Za-z][A-Za-z0-9_-]{0,63}$
- state values: ^[A-Za-z0-9_-]{1,64}$
- No expressions are evaluated anywhere. Values are matched literally.
## Classes the visual applies (style these)
.pbi-is-selected .pbi-is-dimmed .pbi-is-highlighted .pbi-is-open
## Not supported - do not emit, suggest the alternative instead
data-pbi-goto-page -> native button with a Page navigation action
data-pbi-target-visual -> Format > Edit interactions
data-pbi-drillthrough -> data-pbi-context-menu offers it natively
data-pbi-query -> bring the data in through a field well
data-pbi-fetch -> load it into the model
data-pbi-writeback -> the Power Apps visual
data-pbi-script -> express it with this vocabulary
data-pbi-filter -> data-pbi-select to filter the page; a native slicer for a filter that persists; right-click > Include to filter just this visual
## Do not reinvent the platform
<details>, <dialog>, the Popover API, :has(), :target and CSS transitions all work and survive sanitization. Use them directly rather than the vocabulary.
## Style defaults, unless asked otherwise
Segoe UI; white card on a 1px #e1dfdd border, 10px radius, 12-14px padding; uppercase 10px letter-spaced labels in #8b93a8; 26px 650-weight figures; #1d5fb8 for links and actions; green #006300 / red #a02525 for good and bad.Get started in five steps
You need Power BI Desktop and the visual from Microsoft AppSource.
Add the visual
In Power BI Desktop, open the Visualizations pane, select … (more options), then Get more visuals. Search for Interactive HTML and select Add. If you have the .pbiviz file instead, choose Import a visual from a file.
Place it on the page
Select the new icon in the Visualizations pane. With no fields yet, it shows a short message telling you which fields it needs. That is normal.
Fill the three field wells
| Well | Required? | What to put there |
|---|---|---|
| HTML | Yes | The measure that returns your markup. |
| Category | For filtering | A column such as Region. Your markup repeats once per value, each with its own selection. |
| Tooltips | No | Extra values for tooltips and for {{Field}} text. |
Demo Services =
DATATABLE (
"Service", STRING, "Tier", STRING, "MTTR", DOUBLE,
"Incidents", INTEGER, "P1", INTEGER, "Target", DOUBLE,
{
{ "Payments", "Tier 1", 4.2, 47, 3, 6.0 },
{ "Identity", "Tier 1", 7.8, 31, 5, 6.0 },
{ "Data Platform", "Tier 2", 3.1, 22, 1, 6.0 },
{ "Network Core", "Tier 1", 9.4, 58, 8, 6.0 },
{ "Collaboration", "Tier 3", 2.6, 14, 0, 6.0 }
}
)Write your first measure
DAX text uses double quotes, so write HTML attributes with single quotes. Create this measure, put it in the HTML well, and put Sales[Region] in Category. Replace the table and column names with yours.
Interactive Card = VAR Region = SELECTEDVALUE ( Sales[Region] ) VAR Amount = FORMAT ( SUM ( Sales[Amount] ), "#,##0" ) RETURN "<div data-pbi-select data-pbi-tooltip='Click to filter the page' style='padding:14px;border:1px solid #e1dfdd;border-radius:8px; font-family:Segoe UI;background:#fff;cursor:pointer'> <div style='font-size:12px;color:#605e5c'>" & Region & "</div> <div style='font-size:24px;font-weight:600'>" & Amount & "</div> </div>"
Click a card
One card appears per region. Click one and the page filters. Ctrl+click adds more. Click the background to clear. Selected cards get the class pbi-is-selected and the others get pbi-is-dimmed, so you can style both.
The 21 attributes in plain English
Everything else is normal HTML and CSS. Every attribute starts with data-pbi-, and you add it to any element.
Selection
| Attribute | What it does |
|---|---|
select | Filters the page when clicked. Use select="multi" to add to the selection without Ctrl. |
clear | Clears the selection when clicked. An open panel stays open. |
selected | Marks an element to receive pbi-is-selected while its row is selected. |
highlighted | Marks an element to receive pbi-is-highlighted when another visual highlights the row. |
context-menu | Shows Power BI’s right-click menu for the row. |
Panels
| Attribute | What it does |
|---|---|
view="name" | Declares a panel with a name. Place it beside the card, not inside it. |
as | Sets how the panel appears: drawer, modal, inline or tab. |
click | Opens, closes or toggles a panel: open:name, close:name, toggle:name. |
group="name" | Panels in the same group close each other when one opens. |
persist | Saves the panel or state in bookmarks and keeps it when you change pages. |
Tabs and remembered choices
| Attribute | What it does |
|---|---|
set="tab=summary" | Remembers a choice when clicked. |
when="tab=summary" | Shows an element only while that choice is active. Nothing shows until a choice is made, so decide on a default tab. |
Look and context
| Attribute | What it does |
|---|---|
tooltip | Shows a native Power BI tooltip. Use {{Field}} to insert values from the Tooltips well. |
scope | With static, an element stays readable when its card is dimmed. |
theme-color | Uses a report theme colour such as accent1 instead of a fixed colour. |
when-mode | Shows an element only in view or edit mode, for example an author-only note. |
empty | Marks the message to show when there are no rows. |
Power BI actions
| Attribute | What it does |
|---|---|
open-url | Opens a full https:// link in a new tab. Power BI asks the viewer first. |
sort="MTTR:desc" | Reorders the rows already shown. It is instant and runs no new query. |
tooltip-page | Shows a report-page tooltip. The page must be set to “Allow use as tooltip”. |
drill | Best effort only. Power BI Desktop ignores it. Put several fields in Category and use the drill buttons in the visual header. |
Copy-and-paste examples
These use the demo table from step 3, with 'Demo Services'[Service] in Category.
A card that opens its own drawer
Card With Drawer = VAR Svc = SELECTEDVALUE ( 'Demo Services'[Service] ) VAR Mttr = FORMAT ( SUM ( 'Demo Services'[MTTR] ), "0.0" ) RETURN "<div data-pbi-select style='font-family:Segoe UI;padding:12px;border:1px solid #e1dfdd; border-radius:10px;background:#fff;margin:6px;cursor:pointer'> <div style='font-size:11px;color:#8b93a8'>" & Svc & "</div> <div style='font-size:26px;font-weight:650'>" & Mttr & " hrs</div> <span data-pbi-click='open:detail' style='color:#1d5fb8;font-weight:600;cursor:pointer'>Breakdown</span> </div> <section data-pbi-view='detail' data-pbi-as='drawer' style='font-family:Segoe UI;padding:18px;background:#fff'> <span data-pbi-click='close:detail' style='float:right;cursor:pointer;font-size:20px'>×</span> <h3>" & Svc & "</h3> <p>Mean time to repair: <strong>" & Mttr & " hrs</strong></p> </section>"
Tabs with no bookmarks
Tabs =
"<div style='font-family:Segoe UI;padding:12px'>
<nav style='display:flex;gap:16px;border-bottom:1px solid #e7eaf4;padding-bottom:8px'>
<span data-pbi-set='tab=summary' style='cursor:pointer'>Summary</span>
<span data-pbi-set='tab=trend' style='cursor:pointer'>Trend</span>
</nav>
<div data-pbi-when='tab=summary' style='padding-top:12px'>Summary panel</div>
<div data-pbi-when='tab=trend' style='padding-top:12px'>Trend panel</div>
</div>"Avoid these common problems
Use single quotes
Double quotes inside a DAX string end it early. Write style='…'.
Panels sit beside the card
Declare drawers and modals as siblings of the card, not inside it.
Style panels directly
Drawers and modals move in the page. Use inline styles or their own class, not .card .drawer.
Tabs start empty
Nothing shows until a tab is clicked. Choose one tab as the resting state.
Embed your images
The visual makes no web requests. Use data:image/… links. Web images are removed.
Rows stack
Each category row appears under the last. Design a row-style card, not a grid.
Field names change
Power BI may rename a field to “Sum of Incidents”. Both names work in {{ }}. If text shows literally, check the name.
Strict mode removes more
The Strict security setting also removes <svg> and <style>. Keep styling inline.
Questions
Does it cost anything?
No. Interactive HTML is free.
Do the people who open my report need to install it?
No. The visual travels inside the report file, so viewers install nothing.
Is it safe?
Yes. Scripts, event handlers and javascript: links are removed before anything is shown. The visual cannot make network requests, so your report cannot send data out.
It won’t load in my company. Why?
Your Power BI admin controls this. They need to allow visuals created with the Power BI SDK in the tenant settings. If the setting that allows only certified visuals is on, ask your admin to add Interactive HTML under Organizational visuals.
Can I export to PDF or PowerPoint, or send email subscriptions?
Not yet. Power BI only supports certified custom visuals in those routes, and this visual is not certified yet. Everything on screen works normally. If a report must be exported, also show the key numbers in a native visual.
How do I update to a newer version?
Import the newer file over the old one. It keeps the same ID and replaces the old version. Your measures and field settings stay as they are.
Which settings can I change without DAX?
In the Format pane: security strictness, selection limit (default 50, up to 250), maximum rows (default 1,000, up to 30,000), drawer width, dimming, animation, theme, fonts and Debug.
Ready to make your reports interactive?
Get Interactive HTML free, or talk to the BizAcuity team about cards, drawers and dashboards built for your reports.
Get it on AppSourceTalk to BizAcuityThe free six-page sample report on AppSource shows every feature working on built-in data.
