Renders any HTML your DAX returns as a live, styled visual — cards, KPI tiles, mini layouts, badges, whatever a measure can produce as an HTML string. Use it when a native Power BI visual can't quite get the layout, typography, or conditional styling you need, and you're comfortable authoring that styling in a DAX measure instead.
- Turn a single DAX measure into a fully custom-styled card or tile
- Iterate the same HTML template once per row with Group By, so one measure renders a whole grid of cards
- Theme the visual from the format pane — colors flow into the HTML via CSS variables, so templates stay in sync with report theming without editing DAX
- Click a rendered row to cross-filter the rest of the report, same as a native visual
Worth knowing
- One HTML string per row. Without Group By, the visual renders a single block from the first (only) row the measure returns. Add Group By to get one clickable row per category value (e.g., one card per Team or User).
-
HTML is sanitized before it renders. The visual runs everything through DOMPurify.
<script>tags, inline event handlers (onclick, etc.), andjavascript:URIs are always stripped — this is not configurable.<style>blocks and external links are allowed only if the corresponding Advanced toggle is on (see below). -
Theme colors are CSS variables, not just format-pane cosmetics. The eight Theme Colors map to
--pbiv-*custom properties your HTML can reference directly (e.g.background:var(--pbiv-bg)). Set them once in the format pane and every measure that uses the variables stays in sync. - Debug Mode is a build-time tool, not an end-user setting. Turning it on replaces the rendered output with a diagnostic panel (current theme values, row count, raw HTML source, a quick-reference cheat sheet) — turn it off before sharing or publishing the report.
-
<img> tags work, but prefer base64 over external URLs. The sanitizer allows
<img src="...">with either anhttps://URL or a base64data:URI. However, since the manifest declares no privileges (privileges: [], see Limits), the visual has no explicit grant for outbound network calls — an externally-hosted image may render fine in Desktop but fail silently in the Service (or vice versa) depending on sandbox behavior. Embed images as base64data:URIs when you need guaranteed rendering everywhere; treathttps://image URLs as untested until you've confirmed them in your actual target environment.
Contents
- Fields
- Format pane
- Selection & cross-filtering
- Building your first HTML measure
- Debug Mode
- Troubleshooting
- Limits
Fields
| Field | Role | Required | Notes |
|---|---|---|---|
| HTML Value | Measure | Yes | A DAX measure that returns an HTML string. Evaluated once per row when Group By is present, once overall otherwise. |
| Group By | Grouping (one or more columns) | No | The column(s) to iterate over — one row, and one clickable/selectable element, per distinct value. Multiple columns build a compound selection ID (e.g. Team + User). |
If HTML Value is empty, the visual shows a placeholder prompting you to add a measure. If the measure returns no rows or empty strings, it shows "No HTML string returned by the measure."
Format pane
Layout
-
Padding (px) — inner padding applied around the rendered HTML. Default
0. - Transparent Background — when on (default), the visual's own background is transparent so it inherits the page/theme background; turn off to force a solid background.
Theme Colors
Eight color pickers, each exposed to your HTML as a CSS custom property you can use inside inline style attributes or <style> blocks:
| Format pane label | CSS variable | Default | Suggested use |
|---|---|---|---|
| Accent |
--pbiv-accent
|
#0078d4
|
Brand color, links, highlights |
| Background |
--pbiv-bg
|
#ffffff
|
Card/tile background |
| Text |
--pbiv-text
|
#0f172a
|
Primary text |
| Text Muted |
--pbiv-text-muted
|
#64748b
|
Labels, captions, secondary text |
| Border |
--pbiv-border
|
#e2e8f0
|
Dividers, card borders |
| Positive |
--pbiv-positive
|
#16a34a
|
Good / on-track indicators |
| Negative |
--pbiv-negative
|
#dc2626
|
Bad / at-risk indicators |
| Warning |
--pbiv-warning
|
#d97706
|
Caution indicators |
Example:
<div style="background: var(--pbiv-bg); border: 1px solid var(--pbiv-border); border-radius: 6px; padding: 12px 16px;">
<div style="font-size: 11px; color: var(--pbiv-text-muted);">Label</div>
<div style="font-size: 24px; font-weight: 700; color: var(--pbiv-text);">Value</div>
</div>High contrast mode: when Power BI's high-contrast theme is active, the visual overrides all eight variables to the OS-provided high-contrast colors regardless of the format pane settings, so accessibility isn't broken by a custom theme.
Advanced
-
Max Rows — caps how many Group By rows render. Default
1000; clamped between1and30000regardless of what's entered. - Debug Mode — off by default. See Debug Mode below.
-
Allow External Links — on by default. When on,
<a target="_blank">is permitted (the visual auto-addsrel="noopener noreferrer"for safety). When off, links can't open in a new tab. -
Allow Style Blocks — on by default. When on,
<style>tags inside the returned HTML are kept (after stripping@importandurl()references, which are always blocked). When off, any<style>block is removed by the sanitizer.
Selection & cross-filtering
Only active when Group By is used (each row needs a selection ID to filter by):
- Click a row to filter the rest of the report to that value; click it again to clear.
- Ctrl/Cmd-click to multi-select several rows at once.
- Click empty space in the visual to clear the current selection.
- Right-click a row for the standard Power BI context menu (e.g. "Include"/"Exclude").
- Keyboard: arrow keys / Home / End move focus between rows, Enter or Space selects the focused row, Escape clears selection. Full keyboard support means the visual meets standard accessibility expectations for interactive report elements.
- When another visual's cross-filter or a highlight is applied, unselected/non-highlighted rows are visually dimmed rather than hidden, so the full set stays visible for context.
If Allow Interactions is disabled at the report level (e.g. inside a tooltip or a visual with interactions turned off), click/keyboard handling is skipped automatically.
Building your first HTML measure
My Card HTML =
VAR Value_ = [Your Measure]
VAR Label_ = "Label"
RETURN
"<div style="style;" solid="solid">
<div style="font-size: 11px; color: var(--pbiv-text-muted);">" & Label_ & "</div>
<div style="font-size: 24px; font-weight: 700; color: var(--pbiv-text);">" & Value_ & "</div>"
</div>"Drop the measure into HTML Value. Add a column to Group By to repeat the same template once per category, driven by row context inside the measure.
Allowed: standard HTML tags (including <img>), inline CSS, <style> blocks (if enabled), https:// links, base64 data: image URIs.
Always blocked: <script>, inline event handlers (onclick, etc.), javascript: URIs, <iframe>.
Debug Mode
Turning on Advanced → Debug Mode replaces the visual's rendered output with a diagnostic panel showing:
- Group By columns in use, row count, rows currently shown, whether highlights are active, and high-contrast mode status
- The current resolved value of each
--pbiv-*theme variable, with a color swatch - A live preview of row 0's rendered HTML plus its raw HTML source (first 800 characters, escaped)
- An in-visual quick reference: field wells, the CSS variable table, sanitization rules, and the starter measure template above
Useful while authoring a new HTML measure; turn it back off before the report goes to end users, since it disables click/keyboard interaction and shows raw HTML source.
Troubleshooting
| Symptom | Likely cause |
|---|---|
| Visual shows a placeholder prompting for a measure | HTML Value field is empty |
| "No HTML string returned by the measure." | The measure evaluated to blank/empty for every row in scope |
A <style> block you added has no effect
|
Allow Style Blocks is off, or the block used
@import/url(), which is always stripped
|
| A link won't open in a new tab |
Allow External Links is off, or the anchor is
missing target="_blank"
|
| An image doesn't render |
The src is an external https:// URL
and the sandbox blocked the network call — switch to a base64
data: URI (see Worth knowing)
|
| Fewer rows than expected | Max Rows (Advanced) is clamping the Group By output — max is 30,000 regardless of what's entered |
| Clicking a row does nothing | No Group By column is set (selection requires a per-row ID), or Allow Interactions is disabled at the report level (e.g., inside a tooltip) |
Limits
- Max Rows is clamped to 30,000 even if a higher value is entered; the underlying data reduction on the Group By categories is also capped at 30,000 categories.
- The visual renders one HTML string per Group By row — it does not aggregate or merge multiple measure results into one row.
- Requires Power BI custom visual API 5.11.0 or later (set in the manifest); very old Desktop/Service versions may not load it.
- No external network calls or custom privileges are declared (
privileges: []) — the visual only renders HTML/CSS you provide via DAX; it doesn't fetch remote resources on its own.