Create beautiful charts with charts.css in AL

Join me in looking at the CSS-based charting tool chartscss to figure out if you can use it in Business Central (spoiler: you can). Check out the video:

https://youtu.be/FGXBPmXIdmI

In this video, Erik explores Charts.css — a pure CSS framework for rendering beautiful charts — and demonstrates how to integrate it into Business Central using AL’s control add-in (user control) architecture. Along the way, he also tries out the new UserControlHost page type available in BC26 and the multi-line string literal feature.

Why Charts.css?

Business Central offers built-in charting through the business charts control, but it’s generic-looking and offers limited customization. Power BI is powerful but heavyweight — not ideal when you just want a quick, lightweight visualization of real-time data directly on a page.

Charts.css takes a completely different approach: you write a standard HTML <table>, apply some CSS classes, and the framework transforms the table into a chart using nothing but CSS. No JavaScript chart libraries needed.

Setting Up the Control Add-in

The foundation of this approach is a control add-in, which creates an iframe within a Business Central page that can host arbitrary HTML and JavaScript. Here’s the control add-in definition:

controladdin chartcss
{
    MinimumHeight = 200;
    MinimumWidth = 200;
    VerticalStretch = true;
    HorizontalStretch = true;
    StyleSheets = 'https://cdn.jsdelivr.net/npm/charts.css/dist/charts.min.css',
                  'chartscss.css';
    StartupScript = 'chartscss-startup.js';
    Scripts = 'chartscss-functions.js';

    procedure Render(html: Text);

    event ControlReady();
}

Key things to note:

  • The Charts.css stylesheet is loaded directly from a CDN — no need to bundle it locally.
  • A custom chartscss.css file is included for any additional styling.
  • The Render procedure allows AL code to send HTML into the control.
  • The ControlReady event signals AL when the iframe is fully loaded and ready to receive content.

The JavaScript Layer

The control add-in requires two small JavaScript files. The startup script grabs a reference to the container div that Business Central creates inside the iframe, then signals AL that the control is ready:

// chartscss-startup.js
HTMLContainer = document.getElementById("controlAddIn");

Microsoft.Dynamics.NAV.InvokeExtensibilityMethod("ControlReady",[]);

The key detail here is that Business Central renders a single <div> with the id controlAddIn inside the iframe. The call to InvokeExtensibilityMethod fires the ControlReady event back in AL, which is critical because the normal page triggers (like OnOpenPage) often fire before the iframe is ready.

The functions script provides the render capability:

// chartscss-functions.js
function Render(html)
{
    HTMLContainer.insertAdjacentHTML('beforeend', html);
}

This simply injects whatever HTML string AL sends into the iframe’s container div.

The AL Page: UserControlHost

Erik uses the UserControlHost page type, which is designed to give the user control the entire page area — no other page elements to compete with:

page 50100 "Chart.css Test"
{
    PageType = UserControlHost;
    ApplicationArea = all;
    layout
    {
        area(Content)
        {
            usercontrol(chart; chartcss)
            {
                trigger ControlReady()
                var
                    HtmlBuilder: TextBuilder;
                    Customer: Record Customer;
                begin
                    HtmlBuilder.AppendLine('<table class="charts-css column">');
                    HtmlBuilder.AppendLine('    <thead>');
                    HtmlBuilder.AppendLine('    </thead>');
                    HtmlBuilder.AppendLine('    <tbody>');
                    Customer.Setrange("No.", '10000', '50000');
                    Customer.SetAutoCalcFields("Sales (LCY)");
                    if Customer.FindSet() then
                        repeat
                            HtmlBuilder.AppendLine('<tr>');
                            HtmlBuilder.AppendLine('<td style="--size: ' +
                                Customer."Sales (LCY)".ToText() + '">' +
                                Customer."Sales (LCY)".ToText() + '</td>');
                            HtmlBuilder.AppendLine('</tr>');
                        until Customer.Next() = 0;
                    HtmlBuilder.AppendLine('    </tbody>');
                    HtmlBuilder.AppendLine('</table>');
                    CurrPage.chart.Render(HtmlBuilder.ToText());
                end;
            }
        }
    }
}

How Charts.css Works

The magic of Charts.css is that you build a standard HTML table and apply CSS classes to transform its visual presentation:

  1. Create a <table> element
  2. Add the base class charts-css
  3. Add a chart type class: bar, column, area, line, etc.
  4. Optionally add display classes like show-primary-axis, show-data-axes, hide-data
  5. Use the CSS custom property --size on each <td> to set the data value

The --size property expects a unitless number between 0 and 1, representing the proportion of the maximum value. This is an important detail that Erik discovered during the video — if you pass raw sales figures, the chart won’t render correctly because the values need to be normalized.

Static HTML Example

Before building dynamic content, Erik first tested with a static HTML example (shown commented out in the source) using Olympics medal data. This is a good approach — verify the framework works with known-good HTML before adding the complexity of dynamic data generation:

<div id="my-chart">
    <table class="charts-css bar show-primary-axis show-data-axes">
        <caption> 2016 Summer Olympics Medal Table </caption>
        <thead>
            <tr>
                <th scope="col"> Country </th>
                <th scope="col"> Gold </th>
                <th scope="col"> Silver </th>
                <th scope="col"> Bronze </th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <th scope="row"> USA </th>
                <td style="--size: 0.46"> 46 </td>
                <td style="--size: 0.37"> 37 </td>
                <td style="--size: 0.38"> 38 </td>
            </tr>
            ...
        </tbody>
    </table>
</div>

Note how each --size value is already normalized (e.g., 46 medals becomes 0.46).

Custom Styling and Animations

One of the biggest advantages of Charts.css being pure CSS is that you can customize everything with standard stylesheets. Erik’s custom CSS file includes sizing constraints and even animations:

#my-chart {
    width: 100%;
    max-width: 300px;
    margin: 0 auto;
}

#animations-example .column td {
    animation: jumping-bars 3s linear infinite;
}
#animations-example .column tr:nth-of-type(even) td {
    animation-delay: 300ms;
}
@keyframes jumping-bars {
    0% { transform: translateY(   0px ); }
    2% { transform: translateY( -10px ); }
    4% { transform: translateY(   0px ); }
}

The Charts.css documentation includes examples for 3D effects, motion effects, and animations — all achievable with a few lines of CSS.

Building Dynamic Charts from AL Data

The pattern for generating charts from real Business Central data is straightforward:

  1. Use a TextBuilder variable to construct the HTML string
  2. Write the table opening tag with the appropriate Charts.css classes
  3. Write the thead and tbody structure
  4. Loop through your records, creating a <tr> with <td> elements for each data point
  5. Close all tags
  6. Send the completed HTML to the control via CurrPage.chart.Render()

One important consideration Erik discovered: you need to normalize your data values to be between 0 and 1. In a production implementation, you’d first find the maximum value across your dataset, then divide each value by that maximum to get the --size proportion.

Summary

Charts.css offers a lightweight, CSS-only approach to data visualization that integrates surprisingly well with Business Central through the control add-in architecture. The key pieces are:

  • A control add-in definition that loads the Charts.css stylesheet from a CDN
  • Minimal JavaScript for the iframe startup handshake and HTML injection
  • AL code that builds HTML tables with Charts.css classes and --size CSS variables
  • The UserControlHost page type (BC26) for a full-page chart experience

Compared to the built-in business charts control, this approach gives you full control over styling. Compared to Power BI, it’s much more lightweight and shows real-time data without additional infrastructure. For quick, attractive data visualizations embedded directly in your Business Central pages, Charts.css is a compelling option.