AI report HTML framework

AI Assistant report actions (mode: report / report-review) open a host tab and render server-built HTML. Redactor ships a small CSS framework for that HTML so reports look consistent in the tab without inventing one-off markup.

Related: Writing AI Assistant prompts · Backend examples · Using the AI assistant

Contract#

Your backend returns a JSON envelope. The editor uses only the report shell fields for the tab:

{
  "title": "Writing analysis",
  "tab": "writing-analysis",
  "html": "<div class=\"rx-ai-report\">…</div>"
}
Field Role
title Tab label
tab Stable tab key
html Full report markup (styles come from Redactor CSS)

The model should return structured data. Your server turns that data into html using the classes below. Do not ask the model for a free-form page layout.

For report-review, the same html goes in the report tab; issues still drive the review cards.

Naming#

All report classes use the Redactor rx- prefix. CSS variables use --rx-.

Kind Pattern Example
Block rx-ai-report Root wrapper
Element rx-ai-report__… rx-ai-report__finding
Modifier rx-ai-report__…--… rx-ai-report__table--5
Semantic state is-positive / is-negative / is-warning / is-info Meaning, not decoration
Variables --rx-report-… --rx-report-bg, --rx-report-red

Always wrap the whole report in one root:

<div class="rx-ai-report">
  …
</div>

Styles are scoped under .rx-ai-report and ship with redactor.css.

Building blocks#

Class Role
rx-ai-report__header Title area (label + h1 + lead)
rx-ai-report__section Vertical section spacing
rx-ai-report__content Plain prose (h2 + paragraphs)
rx-ai-report__panel Surface box; modifiers --soft, --dark
rx-ai-report__grid Multi-column layout; --3 / --4 / --5 / --flush
rx-ai-report__metric Single KPI inside a grid
rx-ai-report__finding Main reusable insight unit
rx-ai-report__compare Weak / strong side-by-side
rx-ai-report__callout Short highlighted note
rx-ai-report__list Bullets; --plain for unmarked items
rx-ai-report__steps Numbered action list
rx-ai-report__quote Closing pull-quote
rx-ai-report__table CSS grid “table” (not <table>); --2…--5
rx-ai-report__cell Cell inside the table
rx-ai-report__badge Severity / status chip
rx-ai-report__label Small uppercase / muted label
rx-ai-report__lead Supporting paragraph under the title
rx-ai-report__meta Secondary metadata text

Use findings for teachable issues, content for narrative, panels when you need a surface behind lists or steps.

Semantic states#

Attach one state class when the meaning is positive, negative, warning, or informational:

<span class="rx-ai-report__badge is-negative">High</span>
<div class="rx-ai-report__finding is-warning">…</div>
<div class="rx-ai-report__callout is-info">…</div>
<div class="rx-ai-report__cell-value is-positive">Good</div>

States tint text, badges, finding borders, compare columns, and callouts. Prefer meaning over decoration — do not use is-negative only for red styling.

Minimal report#

<div class="rx-ai-report">
  <header class="rx-ai-report__header">
    <div class="rx-ai-report__label">Writing analysis</div>
    <h1>The text feels generic</h1>
    <p class="rx-ai-report__lead">
      It reads smoothly but rarely shows a concrete observation or a clear stance.
    </p>
  </header>

  <section class="rx-ai-report__content">
    <h2>Why it feels templated</h2>
    <p>Most sentences stay at the level of general claims.</p>
  </section>
</div>

Score row (table cells)#

Use the CSS grid table — not HTML <table>:

<section class="rx-ai-report__section">
  <div class="rx-ai-report__table rx-ai-report__table--5">
    <div class="rx-ai-report__cell">
      <div class="rx-ai-report__label">Quality</div>
      <div class="rx-ai-report__cell-value is-negative">Very weak</div>
    </div>
    <div class="rx-ai-report__cell">
      <div class="rx-ai-report__label">Clarity</div>
      <div class="rx-ai-report__cell-value is-warning">Medium</div>
    </div>
    <div class="rx-ai-report__cell">
      <div class="rx-ai-report__label">Structure</div>
      <div class="rx-ai-report__cell-value is-positive">Good</div>
    </div>
  </div>
</section>

Finding#

The primary reusable unit for a check-writing style report:

<div class="rx-ai-report__finding is-negative">
  <div class="rx-ai-report__finding-header">
    <span class="rx-ai-report__badge is-negative">High</span>
    <h2 class="rx-ai-report__finding-title">
      The paragraph opens with a formula, not a thought
    </h2>
  </div>
  <div class="rx-ai-report__finding-body">
    <p class="rx-ai-report__finding-text">
      “It’s a lottery” sounds familiar and does not explain the problem.
    </p>
    <div class="rx-ai-report__finding-action">
      → Start from a concrete observation and name what is happening.
    </div>
  </div>
</div>

Compare (weak / strong)#

<div class="rx-ai-report__compare">
  <div class="rx-ai-report__compare-item is-negative">
    <div class="rx-ai-report__label">Weak</div>
    <div class="rx-ai-report__compare-text">Remote work is a lottery.</div>
  </div>
  <div class="rx-ai-report__compare-item is-positive">
    <div class="rx-ai-report__label">Strong</div>
    <div class="rx-ai-report__compare-text">
      On remote days it is hard to tell when work ends — you just keep sitting with tasks.
    </div>
  </div>
</div>

Metrics, callouts, lists, steps#

<div class="rx-ai-report__grid rx-ai-report__grid--3">
  <div class="rx-ai-report__metric">
    <div class="rx-ai-report__label">Issues</div>
    <div class="rx-ai-report__metric-value is-negative">7</div>
    <div class="rx-ai-report__metric-note">Need attention</div>
  </div>
</div>

<div class="rx-ai-report__callout is-warning">
  <div class="rx-ai-report__callout-title">Watch the first sentence</div>
  <p class="rx-ai-report__callout-text">
    Opening with a cliché costs trust before the argument starts.
  </p>
</div>

<div class="rx-ai-report__panel">
  <div class="rx-ai-report__label">What to change</div>
  <ul class="rx-ai-report__list rx-ai-report__list--plain">
    <li>Replace general claims with observations</li>
    <li>Give one concrete situation per paragraph</li>
  </ul>
  <ol class="rx-ai-report__steps">
    <li>Cut the opening formula.</li>
    <li>Add one specific detail.</li>
    <li>Check that each paragraph carries a new thought.</li>
  </ol>
</div>

Theming#

Override tokens on the root (or a parent) if you need brand colors. Defaults live on .rx-ai-report:

.rx-ai-report {
  --rx-report-bg: #f5f5f5;
  --rx-report-surface: #ffffff;
  --rx-report-text: #202020;
  --rx-report-red: #c83b32;
  --rx-report-green: #26795c;
  --rx-report-orange: #bd7200;
  --rx-report-blue: #356f9f;
  --rx-report-radius: 4px;
  --rx-report-gap: 24px;
}

Keep class names stable so one server HTML builder works for every report action.

PHP helpers (sample endpoints)#

Redactor’s sample backends ship builders that emit this markup:

File Role
endpoints/_ai-report-html.php Primitives: ai_report_wrap, ai_report_header, ai_report_finding, ai_report_compare, ai_report_list, ai_report_steps, …
endpoints/_ai-shared.php Action mappers: ai_report_html_summarize, …_check_writing, … → { title, tab, html }

Axly/Imperavi mirrors the same helpers as lib/ai-report-html.php + lib/ai-shared.php.

Example:

require_once __DIR__ . '/_ai-report-html.php';

$html = ai_report_wrap(
    ai_report_header('Writing analysis', 'Writing check', $lead)
    . ai_report_section(ai_report_finding($title, $text, $action, 'High', 'negative'))
    . ai_report_section(ai_report_compare($weak, $strong))
    . ai_report_section(ai_report_panel(
        ai_report_label('What to do next') . ai_report_steps($actions)
    ))
);

Always escape through ai_html_escape() / the helpers — never concatenate model text raw.

Backend checklist#

  1. Parse the model’s structured JSON for the action.
  2. Escape all user/model text before inserting into HTML.
  3. Build markup only with rx-ai-report* classes (and is-* states).
  4. Wrap everything in a single .rx-ai-report root (ai_report_wrap()).
  5. Return { title, tab, html } (plus issues for report-review).