Sign in Join

Live chat for Free Pascal, Lazarus, Delphi and every Pascal programmer.

Catalog · How the catalog works
Anything · Lazarus or Delphi
Random projectRandom project

CssStyledControls

Lazarus CSS styled controls

View on GitHub

Member rating

No ratings yet.

Sign in to rate this project.

README

CssStyledControls

A set of visual controls for Lazarus / Free Pascal (LCL) that are fully stylable with CSS and support HTML formatting for their text content.

The library brings a modern, web-like approach to desktop GUI development: instead of tweaking dozens of properties by hand, you describe the look of every control in a single CSS file — complete with selectors, pseudo-classes, cascading, specificity, CSS variants (themes), gradients, box shadows, and inline styles. A built-in TCssProxy component can even push the same theme onto standard LCL controls (TForm, TButton, TEdit, TLabel, TPanel).

[!WARNING] Work in progress. The library is under active development. Some controls may not yet be fully functional, and parts of this documentation may be incomplete or out of date. APIs and behavior can change without notice.

[!WARNING] Compatibility note. The library and demo were developed and tested only in Unleashed Pascal and Lazarus stable. Functionality in other versions of FreePascal / Lazarus is not guaranteed.


Table of Contents


Features

  • Full CSS styling — every control reads its appearance from CSS rules: background, gradients, borders, per-corner radii, padding, fonts, colors, alignment, opacity, text shadows, box shadows, cursors, and more.
  • CSS selectors — match by class name, CssTag, CssID, CssClass, pseudo-classes and combinators.
  • Pseudo-classes — :hover, :active, :focus, :focus-visible, :enabled, :disabled, :checked, :unchecked, :default, :cancel.
  • Cascading and specificity — later rules and more specific rules win, exactly like in the browser.
  • Themes / variants — a single CSS file can contain several @variant <name> { ... } blocks (e.g. light and dark), switchable at runtime through TCssStyleProvider.
  • Gradients — linear-gradient(...) and radial-gradient(...) with angles (deg, grad, rad, turn, to …) and color stops.
  • Box shadows — box-shadow: <dx> <dy> <blur> <spread> <color> on any control.
  • Per-corner radii — border-radius accepts 1–4 values (TL TR BR BL).
  • HTML text rendering — HtmlMode renders captions, list items, menu items, tree cells, and hints as HTML with inline styles, lists, headings, code blocks, links, etc.
  • Clickable links — <a href="..."> inside HTML content raises OnLinkClick, changes the cursor, and supports a, a:hover, a:active CSS rules.
  • CSS-styled tooltips — tooltips can be styled with the ::hint selector and rendered as HTML.
  • Windows 11-style switches — set checkbox-style: toggle / radio-style: toggle on a checkbox / radio button (or on a toggle class) to render them as pill switches with an animated thumb. A third synonym, custom, reuses the same toggle renderer but tunes the thumb inset independently through toggle-thumb-inset.
  • SVG check / radio icons — TCssCheckBox, TCssRadioButton, TCssCheckGroup, and TCssRadioGroup accept a TCssSvgImgList and draw per-state icons for every combination of checked / hovered / focused / disabled. When a dedicated disabled icon is not provided, the enabled artwork is desaturated and re-tinted to match the CSS :disabled text colour, exactly like TCssBitBtn.
  • Tree icons from SVG — TCssVirtualStringTree accepts a TCssSvgImgList and requests a per-cell icon through OnGetImageIndex, with DPI-aware sizing, per-theme variants, and automatic currentColor tinting.
  • Nested child styling — composite controls (TCssComboBox, TCssCheckGroup, TCssRadioGroup, TCssListBox, TCssVirtualStringTree, tabs) expose *CssClass / *CssStyle properties so you can style their internal children too.
  • Focus-within — a parent (TCssPanel, TCssGroupBox, TCssCheckGroup, TCssRadioGroup, TCssComboBox, TCssTabControl, TCssPageControl) can highlight its border while a child has focus (focus-within: true).
  • Standard LCL styling — TCssProxy reads the active CSS variant and applies background, color, font-*, and text-align to plain LCL TButton, TEdit, TLabel, TPanel, and forms.
  • Anti-aliased rendering — rounded boxes, circles, triangles, check marks, and focus rings are drawn with sub-pixel coverage and cached bitmaps. SVG icons from TCssSvgImgList are rendered at up to 4× the requested size and box-filtered down with premultiplied alpha, so small icons keep clean diagonal edges.
  • Animated progress bars — TCssProgressBar supports horizontal and vertical orientation, a determinate mode with smooth (eased) transitions when Position changes, an indeterminate pulse mode (pbMarquee) whose amplitude tracks the current Position, three semantic states (normal / error / paused) selectable through CSS colors, and an optional percentage text that automatically switches its color depending on whether it currently sits over the fill or over the track.
  • CSS-styled message dialogs — the CssMessageDialogs unit replaces ShowMessage, MessageDlg, MessageBox, InputBox, InputQuery, and PasswordBox with dialogs built entirely from TCssStyledControl descendants, skinned by the same theme and honouring HTML text. Dialog messages may carry clickable <a href="…"> links, handled through a global or per-dialog callback, and the icon shown next to the message can be taken from a shared TCssSvgImgList instead of the built-in shapes.
  • No external dependencies — pure LCL, works on Windows, Linux, and macOS.

Screenshots

Each tab of the demo project is shown in both variants of the bundled ModernTheme.css — light and dark. In every pair the two screenshots come from the same running form, switched at runtime by the toggle in the status bar; only the active @variant changes.

Common controls

Buttons, TCssBitBtns with built-in and custom SVG glyphs, an HTML label, and a TCssPopupMenu.

Light Dark
Common controls — light Common controls — dark

Checkboxes, Radio, Splitters

Standard checkboxes and radio buttons, Windows 11-style switches (.toggle and .custom), check / radio groups with focus-within, per-state SVG icons on every control and every group item, and a set of panels separated by splitters.

Light Dark
Checkboxes, Radio, Splitters — light Checkboxes, Radio, Splitters — dark

Editors

TCssEdit (plain, disabled, password, read-only), TCssComboBox in all four supported states, TCssListBox, and TCssMemo.

Light Dark
Editors — light Editors — dark

TreeView

Two TCssVirtualStringTrees with columns, per-cell SVG icons, checkboxes, inline editing, and drag & drop between the trees.

Light Dark
TreeView — light TreeView — dark

Message dialogs

Dialogs built by TCssMessageDialogs inherit the same TCssStyleProvider as the rest of the form, so switching the theme restyles them together with the main window — no per-dialog configuration is needed. The pair below shows the same confirmation dialog in both variants; two more dialog examples appear in the Message Dialogs section below.

Light Dark
Message dialog — light Message dialog — dark

Progress bar

Determinate and indeterminate TCssProgressBars in both orientations, with ShowText on and off, and one bar per semantic state — normal, error, paused — so the three fill colors from the active theme are visible side by side.

Light Dark

ListView

Report view with columns, SVG icons, checkboxes, sorting and inline editing, plus list, small-icon and icon views. The report view shows the focus rectangle around the current cell (ShowItemFocusRect), and the lower views use spatial arrow-key navigation between their visual columns.

Light Dark

Installation

  1. Clone the repository:

    bash
    git clone https://github.com/avavdoshin/CssStyledControls.git
  2. Open the runtime package Packages/CssStyledControls.lpk in Lazarus (Package → Open Package File…).

  3. Click Compile, then Use → Install.

  4. Open the design-time package Packages/CssStyledControlsDesign.lpk and install it too. It adds:

    • a multi-line CSS editor for TCssStyleProvider.CssText,
    • a file picker for TCssStyleProvider.FileName,
    • a component editor with an Edit CSS… verb on TCssStyleProvider,
    • a page editor (New Page, Delete Page, Next Page, Previous Page) on TCssPageControl,
    • a visual menu designer for TCssMainMenu / TCssPopupMenu,
    • a Columns Editor for TCssVirtualStringTree.Columns,
    • a shortcut picker (with ~150 common shortcuts) for TCssMenuItem.Shortcut.
  5. The component palette gets a new tab CSSStyledControls:

    TCssStyleProvider, TCssProxy, TCssButton, TCssBitBtn, TCssCheckBox, TCssCheckGroup, TCssComboBox, TCssEdit, TCssGroupBox, TCssLabel, TCssListBox, TCssMemo, TCssMainMenu, TCssPopupMenu, TCssPanel, TCssProgressBar, TCssRadioButton, TCssRadioGroup, TCssScrollBar, TCssSplitter, TCssVirtualStringTree, TCssPageControl, TCssTabControl, TCssTabSheet.

    TCssMenuItem is not shown on the palette: it is created and edited only through the visual menu designer.


Quick Start

  1. Drop a TCssStyleProvider onto a form (or a data module).

  2. Fill its CssText property (or set FileName to a .css file), for example:

    css
    TCssButton {
      background-color: #2563EB;
      color: #FFFFFF;
      border: 1px solid #2563EB;
      border-radius: 6px;
      padding: 6px 14px;
      text-align: center;
      vertical-align: middle;
    }
    TCssButton:hover    { background-color: #1D4ED8; }
    TCssButton:active   { background-color: #1E40AF; }
    TCssButton:disabled {
      background-color: #F1F5F9;
      color: #94A3B8;
      border-color: #E2E8F0;
    }
  3. Drop a TCssButton (or any other TCss* control) onto the same form and assign its StyleProvider property to the provider created in step 1. Every control that references the provider is automatically restyled whenever the CSS text changes.

  4. (Optional) Set HtmlMode := True on a control and use HTML in its Caption:

    html
    <b>Save</b> <span style="color:#f00">now</span>
  5. (Optional) For a light/dark theme, wrap your rules in @variant blocks:

    css
    @variant light
    {
      TCssButton { background-color: #F1F5F9; color: #1E293B; }
    }
    
    @variant dark
    {
      TCssButton { background-color: #334155; color: #E2E8F0; }
    }

    and toggle it in code:

    pascal
    CssStyleProvider1.DefaultStyleName := 'dark';
  6. (Optional) For a button with an icon, use TCssBitBtn:

    pascal
    CssBitBtn1.Kind := bkOK;                          // built-in "OK" icon + label
    CssBitBtn1.Layout := ...                          // set through CSS instead
    css
    TCssBitBtn {
      glyph-layout: left;
      glyph-spacing: 6px;
      glyph-margin: auto;
    }

Demo Project

A ready-to-run demonstration lives in the Demo folder:

pascal
Demo/CssStyledControlsDemo.lpr
Demo/Unit1.pas
Demo/Unit1.lfm

Open CssStyledControlsDemo.lpi in Lazarus and press F9. The demo is wired so that almost everything is driven by CSS and by events — Pascal code is only used where a demo cannot avoid it (populating the tree, switching the theme, copying nodes on cross-tree drops).

All application-wide configuration is performed once in the .lpr file, right after Application.CreateForm and before Application.Run:

pascal
Application.Initialize;
Application.CreateForm(TForm1, Form1);

TCssFormDarkTitle.EnableAutoAttach(Form1.CssStyleProvider1);
CssMessageDlgSetStyleProvider(Form1.CssStyleProvider1);
CssMessageDlgSetHtmlMode(True);
CssMessageDlgSetOnLinkClick(@Form1.OnLinkClick);
CssMessageDlgSetIconSource(Form1.CssSvgImgList1);

Application.Run;

Keeping these calls in the .lpr — rather than in FormCreate — guarantees they are already active on the very first frame, before any form has had a chance to open. The main form itself only contains the event handlers that are genuinely form-local: FormCreate for populating the tree, CssCheckBox1Click for the theme toggle, CssLabel2LinkClick and OnLinkClick for the link demo, and the tree's own editing and drag-and-drop events.

The form is organised as a TCssPageControl with six tabs, plus a bottom status panel and a top menu bar. All dialogs shown during the demo are produced by the TCssMessageDialogs unit, so they are styled by the same TCssStyleProvider that skins the main form — there is no native LCL dialog anywhere on screen.

Page 1 — Common controls

  • A TCssPageControl whose tab strip is decorated with SVG icons. The page itself carries the icon (CssTabSheet1.ImageIndex = 1), so the tab automatically displays the second entry of the shared CssSvgImgList1 (icons8-news). The icon follows the tab text color in every state — plain, hovered, active — and switches to the dark variant of that entry together with the CSS theme.

  • A TCssGroupBox containing several TCssButtons (enabled, disabled, default, cancel, and one with a TCssPopupMenu).

  • The first button in that group (CssButton1, the one labelled Show all dialogs) opens a step-by-step demonstration of the TCssMessageDialogs unit. Its hint reads "Click to see all CssMessageDlg functions"; the handler walks through fourteen consecutive dialogs, one per click, closing each one to see the next:

    1. Inline formatting (,, <u>, <s>, <code>).
    2. Inline colors via and the legacy.
    3. Headings <h1>..<h3> with the icon centred against the first line of text.
    4. Paragraphs, automatic word wrapping and explicit `

    `.

    1. Ordered and unordered lists.
    2. Preformatted code in <pre>, preserving spaces and #10.
    3. combined with and align="right".
    4. Subscript, superscript and <q> inline quotes.
    5. Hyperlink styling for <a href="...">.
    1. A confirmation dialog mixing a heading, a list and a colored warning paragraph, with mbNo as the safe default.
    2. A long wrapped HTML paragraph with mixed inline runs.
    3. A release-notes style combination ending with a centred `` and an italic footer.
    4. An HTML prompt rendered through CssInputBox, showing that the same parser drives the input-dialog caption as well.
    5. A confirmation dialog that mirrors #10 but uses a custom SVG icon through CssMessageDlgIcon. The icon comes from index 22 of the shared CssSvgImgList1 — the icons8-not-synced artwork — and is drawn in place of the built-in warning triangle. This demonstrates that an arbitrary entry of the icon list can be shown in a dialog without redefining the global source.

    Every dialog in this sequence is built from the same styled controls as the rest of the library: a TCssPanel backdrop with border-radius, a TCssMessageIcon, a TCssLabel with HtmlMode, a one-pixel divider, and TCssButtons that inherit :default and :cancel from the theme. Dialog #14 additionally exercises the per-call SVG override (CssMessageDlgIcon(..., 22, ...)), which needs the application-wide icon source to be configured once — the demo does this in FormCreate with CssMessageDlgSetIconSource(CssSvgImgList1).

    When the theme is switched from light to dark, the whole sequence is restyled automatically — no per-dialog configuration is required. Icons from the shared list are re-rasterised with the new currentColor, and any entry that defines a dark variant is swapped to that variant together with the rest of the dialog.

    HTML mode is turned on for the duration of the sequence with CssMessageDlgSetHtmlMode(True) and turned off in a finally block, so the rest of the application keeps interpreting < and > as ordinary characters.

  • Four TCssBitBtn controls next to those buttons, covering every glyph source the control supports:

    • Kind = bkCancel with a custom caption (UseKindCaption = False) — a built-in vector icon paired with a user-supplied label.
    • the same Kind = bkCancel, but Enabled = False — the same glyph is rendered in its muted disabled variant, in tone with the disabled caption.
    • a custom SVG glyph (SvgImages = CssSvgImgList1, ImageIndex = 0) — a 32×32 vector icon rasterised at the current DPI and drawn next to an HTML caption (Custom SVG, HtmlMode = True).
    • the same SVG glyph with Enabled = False — the rasteriser desaturates the RGBA image in place (transparent pixels stay transparent, anti-aliased edges are preserved) and re-tints it to match the disabled text color, so no box appears behind the icon.
  • A non-visual TCssSvgImgList (CssSvgImgList1) holding the SVG sources for the whole demo. It is configured at 32 × 32 design-time size with Scaled = True, so every icon is always rendered at the current DPI, and ships with seven 48×48 general-purpose icons, sixteen 24×24 state icons for the checkbox and radio demos, and one additional message-dialog icon:

    General icons (indices 0–5, used by buttons, tabs, menus, and trees):

    • icons8-ok — a gradient circle with a white checkmark. This entry has per-theme variants: its base Svg is the light artwork, and its Variants list carries an alternative dark body with a muted green gradient.
    • icons8-news — a blue newspaper-style icon, used by the tab strip.
    • icons8-edit — a pencil-on-paper icon, used by MenuItem1 in the popup menu.
    • icons8-done, icons8-refresh, icons8-share — additional entries used by the two trees on the TreeView tab.

    Checkbox state icons (indices 6–13, used by the Custom panel on the Checkboxes, Radio, Splitters tab):

    • check_unchecked — plain rounded square.
    • check_checked — square with a white checkmark.
    • check_unchecked_hover / check_checked_hover — same two shapes in the darker hover tint.
    • check_unchecked_focused / check_checked_focused — same two shapes wrapped in the focus ring.
    • check_unchecked_disabled / check_checked_disabled — muted grey variants for the :disabled state.

    Radio state icons (indices 14–21, used by the same Custom panel):

    • radio_unchecked, radio_checked, radio_unchecked_hover, radio_checked_hover, radio_unchecked_focus, radio_checked_focus, radio_unchecked_disabled, radio_checked_disabled — the eight circle-based counterparts of the checkbox set, keeping the same state coverage (normal, hover, focus, disabled).

    The state icons are all pure-fill SVG (no stroke) with integer coordinates, so they rasterise cleanly at every DPI without the smearing that thin strokes would produce at 22-pixel sizes.

    Message-dialog icon (index 22, used by scenario #14 on Page 1):

    • icons8-not-synced — a red "no sync" glyph exported from Illustrator. The file wraps two full-canvas background rectangles in <g> groups marked style="display:none;". The SVG parser skips those hidden subtrees together with their children, so the rasterised output contains only the visible Layer_3 and shows clean transparency on the dialog backdrop. This entry doubles as a live demonstration of the parser's hidden-group handling.
  • A TCssPopupMenu (CssPopupMenu1) whose icons are sourced once at the menu level: CssPopupMenu1.SvgImages := CssSvgImgList1. Every item can then simply set its own ImageIndex — MenuItem1 uses 2 (icons8-edit), and MenuItem2 leaves it at -1, so the icon column is not reserved when no item in the menu has an icon.

  • A TCssPanel with a TCssLabel whose Caption is HTML demonstrating every supported tag: headings, bold / italic / underline / strike / code / kbd / samp / tt / sup / sub / <q>, alignment via both align="…" and style="text-align:…", ordered and unordered lists, inline colors, legacy ``, spans, and clickable <a> links.

Page 2 — Checkboxes, Radio, Splitters

The tab is organised as four TCssPanels stacked top-to-bottom, each demonstrating one visual style, followed by a fifth panel that shows the splitters.

  • Standart panel — plain TCssCheckBox / TCssRadioButton with the default box-and-check-mark renderer, and a TCssCheckGroup / TCssRadioGroup pair with the same style. Two more groups (CssCheckGroup3, CssRadioGroup3) are disabled and carry no caption, demonstrating how :disabled propagates to the children.
  • Toggle panel — the same six controls, but every one has CssClass = 'toggle' (standalone) or CheckBoxCssClass / RadioCssClass = 'toggle' (groups). This is the Windows 11-style pill switch, driven by the .toggle rules in the theme. The disabled groups at the right show the muted palette in that style as well.
  • Custom panel — the visual counterpart of Toggle, but every control draws its glyph from CssSvgImgList1 instead of the built-in renderer. The standalone checkboxes (CssCheckBox6, CssCheckBox7) and the checkbox groups (CssCheckGroup5, CssCheckGroup6) use indices 6–13; the standalone radios (CssRadioButton5, CssRadioButton6) and the radio groups (CssRadioGroup5, CssRadioGroup6) use indices 14–21. Every control wires all eight image indexes — ImageIndex, ImageIndexChecked, ImageIndexHover, ImageIndexCheckedHover, ImageIndexFocused, ImageIndexCheckedFocused, ImageIndexDisabled, ImageIndexCheckedDisabled — so hovering, focusing, and disabling each swap the glyph through the SVG list. The two disabled groups on the right use the same *Disabled indexes, so their artwork is muted without any per-control desaturation code.
  • Panels and splitters panel — one horizontal splitter between a Top panel and a Bottom panel, and one vertical splitter between a Left panel and a Right panel. The panels demonstrate focus-within: true and the hover border transition.

Because the Custom panel is driven entirely by TCssSvgImgList and CSS classes, switching the active CSS variant from Light to Dark also swaps the checkbox / radio glyphs to their per-theme variants when the corresponding SVG entries define them — no Pascal code runs during the switch.

Page 3 — Editors

  • Four TCssEdit controls: normal, disabled, password (PasswordChar = '*', MaxLength = 10), and read-only (Caption preset, all editing disabled). Every one uses the placeholder-color / placeholder-font-style declarations from CSS to render a muted italic placeholder.
  • Four TCssComboBox controls covering both combo styles:
    • ccsDropDown — editable, HTML items, Placeholder = 'Enter or choose value'.
    • ccsDropDown (disabled) — same, with the muted palette from :disabled.
    • ccsDropDownList — selection-only, ten items, Placeholder = 'Choose value'.
    • ccsDropDownList (ReadOnly = True) — same, but the dropdown cannot be opened and the arrow keys do not change the selection.
  • Three TCssListBox controls: a short list, a long list with a custom scrollbar (.listbox-scrollbar), and a disabled list with a pre-selected item to show the disabled selection colors.
  • A TCssMemo with ssAutoBoth scrollbars and .memo-scrollbar styling, filling the rest of the tab.

Page 4 — TreeView

  • Two TCssVirtualStringTrees (LeftTree and RightTree) configured identically, so that any node can be dropped from one into the other. Each tree has:
    • four columns — Name, Kind, Note, Size — with HTML headers (Name, Note, <u>Size</u>), per-column alignment (CellHAlign = thaCenter on Kind, thaRight on Size), and LastColumnStretch = True so the Size column fills the remaining width.
    • per-cell SVG icons in the Kind column, sourced from CssSvgImgList1 through OnGetImageIndex. The event returns D^.Kind for column 1 and -1 for the others, so the icon column is reserved only where icons actually exist.
    • checkboxes (ShowCheckboxes = True) with the .css-tree-checkbox .checkbox class inherited from the base TCssCheckBox.
    • inline editing (AllowEditing = True). OnEditing blocks column 3 (Size) so the numeric column stays read-only; OnNewText writes the typed value back to the correct field based on Column, and applies PlainTextToHtml only to column 0.
    • drag & drop (AllowDrag = True, AllowDrop = True). Dropping a node (or a multi-selection) onto another tree creates a full copy of the subtree in the target and deletes the original from the source, preserving cvsChecked, cvsExpanded, and cvsDisabled states. OnDragOverNodes prevents dropping on a disabled node, and rejects dragging disabled nodes at all.
    • a placeholder shown when the tree is empty (Drop items from left tree here / …from right tree here).
  • The left tree is populated in FormCreate with three root groups (Fruits, Vegetables, Berries) and a mixture of HTML captions, notes, icon indexes, and sizes. One node (Pears) is disabled to demonstrate the muted palette for :disabled rows.

Page 5 — Bars

  • Horizontal determinate panel — six TCssProgressBars at 10%…60%, covering the three semantic states (pbsNormal, pbsPaused, pbsError) in both their enabled and disabled palettes. Their Hint names the state, so hovering is enough to identify each bar.
  • Horizontal marquee panel — the same six state/disabled combinations with Style = pbMarquee, showing the animated indeterminate pulse whose amplitude follows Position.
  • Vertical marquee panel — six vertical bars (Orientation = pbVertical) with the same state coverage.
  • Interactive panel — a smooth determinate bar, a marquee bar and a bar with smooth transitions turned off, driven by the Increase / Decrease buttons (CssButton6 / CssButton7). The buttons clamp the position between 0 and 100 and update all three bars at once, so the difference between eased and immediate updates is visible.
  • Vertical determinate panel — vertical bars at 10%, 50% and 100%, plus a 100% marquee bar and the paused/error variants at full position.

Page 6 — ListView

  • CssListView1 demonstrates the report view (ViewStyle = lvsReport) with fourteen items and five columns: Name (300 px), Kind (110 px, centred), Note (360 px), Size (90 px, right-aligned) and Modified (220 px). It enables Checkboxes, GridLines, HotTrack, RowSelect, MultiSelect, HideSelection = False and AutoSort = True. Every row carries an SVG icon from CssSvgImgList1 through Item.ImageIndex; the icons are rasterised at 16×16 (SmallImages, SmallImageWidth/Height).

  • Sorting — clicking the Kind header toggles ascending / descending order and paints the sort-marker-color arrow in the header. No Pascal sorting code is involved; AutoSort uses the control's built-in comparison.

  • Inline editing — double-clicking a row starts the editor on column 0 (Name). The editor is styled by .listview-edit; pressing Enter commits the text and OnEdited (CssListView1Edited) copies the new caption to the matching item in the other three views, which hold independent item objects.

  • Keyboard navigation — Up/Down move the selection between rows, while Left/Right move the focused cell between columns without changing the selected row. ShowItemFocusRect = True draws the dotted focus rectangle around the current cell, and clicking a cell also updates the focused column.

  • HTML text — CssListView1.HtmlMode = True, so the column headers and the Note cells render HTML: bold, italic, underline, and inline colors are visible in the report list. Sorting and AutoSizeColumn use the plain text without the tags.

  • Other view styles — three read-only TCssListViews on the lower half of the page use the same data:

    • CssListView2 — lvsList with a single 160 px column;
    • CssListView3 — lvsSmallIcon with 16×16 artwork;
    • CssListView4 — lvsIcon with 40×40 artwork (LargeImages, LargeImageWidth/Height).

    Keyboard navigation in these views follows the visible layout: Up/Down move inside the current visual column and Left/Right move to the same row of the adjacent column, falling back to that column's last item when the row does not exist.

  • The embedded scrollbars use .listview-scrollbar, and the inline editor uses .listview-edit. Both the embedded demo stylesheet and Themes/ModernTheme.css define the light and dark variants of TCssListView, so switching the theme restyles the header, rows, selection, grid lines, check marks and scrollbars without any code.

Status bar and menu

  • A TCssCheckBox styled as a Windows 11 switch (.toggle class) that switches the whole application between the light and dark variants by setting CssStyleProvider1.DefaultStyleName. Its Hint is itself written in HTML — <h6>, `` and <li> render through the CSS-styled hint window.
  • A TCssMainMenu with File and Themes dropdowns. Switch theme (MenuItem5) toggles the same switch as the status bar, and the File menu carries a separator to show how menu-separator-color and menu-separator-height are applied.
  • A TCssProxy that applies the same theme to the plain TForm.

The relevant demo handlers are tiny:

pascal
procedure TForm1.CssCheckBox1Click(Sender : TObject);
begin
  if CssCheckBox1.Checked then
    CssStyleProvider1.DefaultStyleName := 'Dark'
  else
    CssStyleProvider1.DefaultStyleName := 'Light';
end;

procedure TForm1.CssLabel2LinkClick(Sender : TObject;
  const AHref, AText : UnicodeString);
begin
  CssMessageBox('Link clicked',
    'Href=' + AHref + ' , Text=' + AText,
    MB_YESNOCANCEL or MB_ICONINFORMATION);
end;

procedure TForm1.LeftTreeNewText(Sender : TObject; Node : TCssVirtualNode;
  Column : Integer; const NewText : string);
var
  D: PNodeData;
  Tree: TCssVirtualStringTree;
begin
  if not Assigned(Node.Data) then Exit;

  D := PNodeData(Node.Data);
  Tree := TCssVirtualStringTree(Sender);

  case Column of
    0: D^.Caption := Tree.PlainTextToHtml(NewText);
    2: D^.Note    := Tree.PlainTextToHtml(NewText);
    3: D^.Size    := StrToIntDef(Trim(NewText), D^.Size);
  end;
end;

procedure TForm1.LeftTreeDropNodesEx(Sender : TObject;
  SourceTree : TCssVirtualStringTree; Nodes : TList;
  TargetNode : TCssVirtualNode);
begin
  // Cross-tree drop: copy every node into the target, then delete the
  // originals from the source. Same-tree drops fall back to the built-in
  // MoveNodes.
end;

Note the second handler: clicking an <a> inside the HTML label now opens a CssMessageBox from TCssMessageDialogs, so even the "reference to a link" popup is skinned by the same theme and inherits the MB_ICONINFORMATION glyph from the CSS-aware dialog engine. The Dialogs unit is still present in the uses clause, but only for the mrYes / mrNo / mrCancel result constants — none of the standard ShowMessage / MessageDlg / MessageBox functions are called from the demo.

The dialog engine used by Page 1's Show all dialogs button is configured once, in the .lpr file, right after the main form is created:

pascal
Application.Initialize;
Application.CreateForm(TForm1, Form1);

TCssFormDarkTitle.EnableAutoAttach(Form1.CssStyleProvider1);
CssMessageDlgSetStyleProvider(Form1.CssStyleProvider1);
CssMessageDlgSetHtmlMode(True);
CssMessageDlgSetOnLinkClick(@Form1.OnLinkClick);
CssMessageDlgSetIconSource(Form1.CssSvgImgList1);

Application.Run;

Four of these calls configure the dialog engine for the whole application at once:

  • CssMessageDlgSetStyleProvider binds the same TCssStyleProvider the main form uses, so a dialog is skinned by the same theme.
  • CssMessageDlgSetHtmlMode turns on the HTML parser for the message text and for the input prompt.
  • CssMessageDlgSetOnLinkClick installs TForm1.OnLinkClick as the global handler for <a href="..."> links, so a click in any dialog is routed through the same method as a click in the on-form label.
  • CssMessageDlgSetIconSource registers the shared CssSvgImgList1 as the source for the dialog icons. This is what makes CssMessageDlgIcon('...', ..., 22, ...) in scenario #14 show the icons8-not-synced artwork; without it, the call would silently fall back to the built-in shapes.

TCssFormDarkTitle.EnableAutoAttach is a separate concern — it makes every form that becomes visible follow the same CSS theme on its native Windows title bar. See Native Form Title Bar above.


Switching the toggle changes the CSS theme and every SVG icon at the same time — the tab-strip icon, both TCssBitBtn glyphs, the popup menu item icon, and every cell icon in both trees all move to their dark variants in one step, without a single line of code in the form.

Included Controls

Control Description
TCssStyledControl Base class of all CSS-styled controls. Contains the CSS engine, the HTML parser, and the AA renderer.
TCssStyleProvider Non-visual component that holds CSS text and pushes it to controls.
TCssSvgImgList Non-visual component that stores images in native SVG format, optionally with per-theme variants (dark, light, …), and renders them on demand with HiDPI and anti-aliasing. Drop-in replacement for TImageList when vector icons are preferred.
TCssProxy Non-visual component that applies the active CSS variant to plain LCL controls (TForm, TButton, TEdit, TLabel, TPanel).
TCssButton Push button with Default, Cancel, ModalResult, and :default / :cancel pseudo-classes.
TCssBitBtn Button with a glyph (icon) next to the caption. Supports standard TBitBtn.Kind icons, custom bitmaps (Glyph + NumGlyphs), TImageList, TCssSvgImgList, per-state image indexes (ImageIndexDisabled / Pressed / Focused), HiDPI-aware rendering, and CSS-driven icon layout.
TCssCheckBox Tri-state check box with an optional Windows 11-style switch appearance.
TCssCheckGroup Group of check boxes laid out in columns, with per-item state.
TCssRadioButton Radio button with an optional Windows 11-style switch appearance.
TCssRadioGroup Group of radio buttons laid out in columns.
TCssComboBox Dropdown combo box (ccsDropDown, ccsDropDownList).
TCssEdit Single-line editor with selection, clipboard, undo, placeholder styles.
TCssMemo Multi-line editor with word-wrap, scrollbars, selection, clipboard, colored caret and placeholder.
TCssLabel Label with FocusControl, ShowAccelChar, Transparent.
TCssListBox List box with multi-select, hover, per-item colors, custom scrollbar.
TCssListView List view with report / list / small icon / icon styles, columns, sub-items, checkboxes, SVG images, sorting, inline editing, multi-select, and custom scrollbars.
TCssPanel Container panel with focus-within support.
TCssProgressBar Progress bar with horizontal / vertical orientation, a determinate mode with eased position transitions, an indeterminate pulse mode, and three semantic states (normal / error / paused).
TCssGroupBox Group box with caption on the border and focus-within support.
TCssScrollBar Custom scrollbar (can be drawn standalone or inside another control).
TCssSplitter Splitter with grip, live/ghost preview, and adjacent-control highlighting.
TCssMainMenu Menu bar with dropdowns.
TCssPopupMenu Popup menu component (non-visual) with Popup / AutoPopup.
TCssMenuItem Single menu item. Hidden from the palette; edited by the visual menu designer.
TCssTabControl Tab strip without pages, with optional overflow scroll buttons.
TCssPageControl Tab control with pages (TCssTabSheet).
TCssTabSheet A single page of a TCssPageControl.
TCssVirtualStringTree Virtual tree view with columns, checkboxes, SVG cell icons, editing, sorting, drag & drop, per-cell alignment, lazy loading, and incremental search.
---

CSS Overview

Where the CSS Lives

There are three ways to give a control its CSS rules, applied in this order (later sources override earlier ones):

  1. TCssStyleProvider — the recommended way for an application-wide theme. Assign the provider to a control's StyleProvider property.
  2. StyleName — selects a specific variant inside the provider. If empty, the provider's DefaultStyleName is used.
  3. CssStyle — inline declarations applied after everything else. Equivalent to the HTML style="..." attribute.

Additionally, CssTag, CssID, and CssClass define how the control is matched by selectors.

Selectors

A selector is a chain of simple selectors. The engine parses the last simple selector in the chain (descendant/child combinators are accepted but only the rightmost part is matched, like in simplified CSS).

Supported simple selector components:

Syntax Matches
* Any control.
Name The control's class name (TCssButton), CssTag, 'control', or 'TCssStyledControl'.
#id CssID.
.class One of the classes in CssClass (space-separated list, several allowed).
:pseudo A pseudo-class (see below).
a, a:hover, a:active Rules for HTML links inside HtmlMode text.
::hint Rules for the control's tooltip.

Example:

css
TCssButton                 { /* by class name */ }
button.primary             { /* by tag + class  */ }
#saveButton                { /* by id           */ }
TCssButton:hover           { /* pseudo-class    */ }
a                          { /* html link       */ }
a:hover                    { /* hovered link    */ }
::hint                     { /* tooltip         */ }
.toggle                    { /* class selector  */ }

Pseudo-Classes

Pseudo-class Active when…
:hover The mouse is over the control.
:active The left mouse button is pressed on the control.
:focus The control has focus.
:focus-visible Same as :focus.
:enabled Enabled = True.
:disabled Enabled = False.
:checked Checked = True.
:unchecked Checked = False.
:default (TCssButton only) Default = True.
:cancel (TCssButton only) Cancel = True.

Specificity and Cascade

Rules are sorted by a simplified specificity tuple, then by source order:

  1. A — number of #id parts (0 or 1).
  2. B — number of .class and :pseudo parts.
  3. C — 1 if a type/tag name was present, else 0.

If everything is equal, rules that appear later in the CSS text win. Inline styles (CssStyle) are applied last of all.

Themes (Variants)

A single CSS text may contain several named variants. Each variant starts with a marker:

css
@variant light
{
  TCssButton { background-color: #2563EB; color: #FFFFFF; }
}

@variant dark
{
  TCssButton { background-color: #3B82F6; color: #FFFFFF; }
}

The alternative bracket syntax is also accepted:

css
[light]
TCssButton { /* ... */ }

[dark]
TCssButton { /* ... */ }

Everything between one @variant marker and the next belongs to that variant. Rules placed before the first marker belong to DefaultStyleName.

At runtime, choose the theme by setting the provider's DefaultStyleName or a control's StyleName:

pascal
CssStyleProvider1.DefaultStyleName := 'dark';
// or per control:
CssButton1.StyleName := 'dark';

A file without any @variant marker is treated as a single variant named by DefaultStyleName ('default' by default).


General CSS Properties

These declarations are understood by every TCssStyledControl descendant.

Background

Property Example Notes
background background: #fff; Shorthand; picks the first color token, or a gradient if the value is *-gradient(...).
background-color background-color: transparent; transparent means "do not fill".
background-image background-image: linear-gradient(…) Supports linear-gradient(...) and radial-gradient(...).

Gradients

css
TCssPanel {
  background-image: linear-gradient(180deg, #2563EB 0%, #1E40AF 100%);
}

TCssPanel.radial {
  background-image: radial-gradient(#FFFFFF 0%, #CBD5E1 100%);
}

Supported angle units: deg, grad, rad, turn, and the to <side> / to <corner> keywords. Color stops accept % positions; missing positions are interpolated automatically.

Text

Property Values Notes
color CSS color Text color.
text-align left | center | right Horizontal alignment of caption/text.
vertical-align top | middle / center | bottom Vertical alignment.
text-shadow 1px 1px #000 / none <dx> <dy> [color].
font italic bold 12pt 'Segoe UI' Shorthand; the first recognized size wins.
font-family 'Segoe UI', Arial, sans-serif First family found on the system wins; keyword families are resolved.
font-size 12pt | 14px | medium | large … pt is converted to px.
font-weight normal | bold | 100…900 600 and above ⇒ bold.
font-style normal | italic | oblique

Generic families serif, sans-serif, monospace, cursive, fantasy, and system-ui are mapped to real fonts on the current system.

Border

Property Example Notes
border 1px solid #ccc Shorthand for width / style / color.
border-width 1px, 2px, thin, medium, thick
border-color #cccccc
border-style none | solid | dotted | dashed
border-radius 6px | 6px 4px | 6px 4px 2px | 6px 4px 2px 0 1–4 values, CSS shorthand (TL TR BR BL).

Spacing

Property Notes
padding 1–4 values, CSS shorthand.
padding-top, padding-right, padding-bottom, padding-left Individual sides.

Interaction and Effects

Property Values Notes
cursor pointer, text, wait, move, not-allowed, no-drop, help, crosshair, progress, e-resize, w-resize, n-resize, s-resize, ne-resize, sw-resize, nw-resize, se-resize, col-resize, row-resize, default, … Maps to LCL cursor constants.
opacity 0.0 … 1.0 (or 50%) Blends the control's colors with its parent color.
focus-color CSS color Color of the focus rectangle.
focus-within true | false (Composite controls) highlight the parent while a child has focus.
box-shadow 2px 2px 4px 1px rgba(0,0,0,.25) <dx> <dy> [blur] [spread] [color]. Supports inset (parsed and ignored).

Colors

The following formats are accepted anywhere a color is expected:

  • #RGB, #RGBA, #RRGGBB, #RRGGBBAA
  • rgb(r, g, b), rgba(r, g, b, a) with values 0..255 or 0..100%
  • Named colors: black, white, red, green, blue, yellow, orange, purple, gray / grey, silver, maroon, olive, lime, aqua / cyan, teal, navy, fuchsia / magenta, pink, brown, gold, khaki, beige, ivory, snow, tomato, coral, salmon, darkgray, lightgray, dimgray, whitesmoke
  • transparent

Lengths

Lengths accept px, pt (converted to px at 96 DPI), unit-less numbers (treated as px), and the keywords thin (1), medium (2), thick (4).

Per-Control CSS Properties

Each control adds its own set of declarations on top of the general ones. All of them are reset by ResetStyle and can be overridden via :hover, :active, :focus, :disabled, :checked, etc.

TCssButton

No extra properties. Use the standard pseudo-classes plus :default and :cancel.

Additional Pascal properties: Default, Cancel, ModalResult.

css
TCssButton:default { background-color: #2563EB; color: #FFFFFF; }
TCssButton:cancel  { background-color: #FEF2F2; color: #B91C1C; }

TCssBitBtn — Button with Glyph

TCssBitBtn is a descendant of TCssButton that adds a bitmap glyph next to the caption, in the spirit of the classic TBitBtn. Because it inherits from TCssButton, every TCssButton rule in your CSS still applies — the bit button only adds three extra glyph-* properties and the ability to source an icon.

Glyph Source Priority

The control can draw a glyph from four sources, in this order (highest priority first):

  1. Kind ≠ bkCustom — a built-in vector icon, plus a standard caption.
  2. SvgImages + ImageIndex — an image from a TCssSvgImgList. The SVG is rasterised on demand, at the current DPI, with anti-aliasing, and tinted with the effective text color from CSS.
  3. Images + ImageIndex — an image from a TImageList.
  4. Glyph — a user-supplied TBitmap, optionally split into 1–4 states via NumGlyphs.

As soon as a source yields a valid image index, the next sources in the list are ignored. When no glyph is available at all, TCssBitBtn behaves exactly like a plain TCssButton.

Pascal Properties

Property Type Default Notes
Kind TCssBitBtnKind bkCustom One of: bkCustom, bkOK, bkCancel, bkHelp, bkYes, bkNo, bkClose, bkAbort, bkRetry, bkIgnore, bkAll.
Glyph TBitmap empty User bitmap. Assigning a non-empty bitmap resets Kind to bkCustom.
NumGlyphs Integer 1 1–4: how many sub-images the Glyph bitmap is split into, stacked vertically. Ignored for SvgImages, Images and Kind.
Images TCustomImageList nil Source of the glyph when Kind = bkCustom and no SvgImages is set.
SvgImages TCssSvgImgList nil Preferred source of the glyph when Kind = bkCustom. Takes priority over Images. See SVG Image Lists.
ImageIndex Integer -1 Index for the normal state, inside SvgImages or Images. -1 disables this source.
ImageIndexDisabled Integer -1 Index for the disabled state. -1 means "fall back to ImageIndex". See below.
ImageIndexPressed Integer -1 Index for the pressed (:active) state. -1 means "fall back to ImageIndex".
ImageIndexFocused Integer -1 Index for the focused state. -1 means "fall back to ImageIndex".
Transparent Boolean True Whether Glyph uses transparency.
GlyphScaled Boolean False If True, Glyph and Images are stretched by the current DPI factor. The SvgImages and Kind sources are always DPI-scaled.
UseKindCaption Boolean True If True, changing Kind overwrites Caption with the standard label. If False, your own caption is preserved. Automatically set to False the first time you assign a caption that does not match the current Kind's default.
GlyphLayout TCssButtonLayout blGlyphLeft Read-only. Reflects the effective value parsed from CSS.
GlyphSpacing Integer — Read-only. Effective spacing in device pixels.
GlyphMargin Integer — Read-only. Effective margin in device pixels, or -1 for "use CSS padding".

Per-State Image Indexes

For the SvgImages and Images sources, ImageIndex selects the glyph for the normal state, and the three extra properties override it for the specific states:

State Index used
Normal ImageIndex
Enabled = False ImageIndexDisabled, if >= 0; otherwise ImageIndex
Pressed (:active) ImageIndexPressed, if >= 0; otherwise ImageIndex
Focused ImageIndexFocused, if >= 0; otherwise ImageIndex

Only one state applies per paint. The lookup order matches the glyph state machine: disabled → pressed → focused → normal, so a disabled button always uses ImageIndexDisabled even if it is also focused or pressed.

If a dedicated disabled image is provided via ImageIndexDisabled, that image is used as is — the automatic muted-tint treatment described below is skipped. This is the recommended way to ship hand-crafted disabled artwork.

NumGlyphs and Glyph States

When a user Glyph is assigned, NumGlyphs tells the control how the source bitmap is split. States are selected automatically:

NumGlyphs State 0 (normal) State 1 (disabled) State 2 (pressed) State 3 (focused)
1 ✓ — — —
2 ✓ ✓ — —
3 ✓ ✓ ✓ —
4 ✓ ✓ ✓ ✓

For Kind icons, SvgImages and Images, NumGlyphs is ignored: those sources supply a single glyph per state, and the disabled variant is generated internally if no explicit ImageIndexDisabled is set (see below).

Disabled State

When Enabled = False:

  • If the glyph source is Kind, the control regenerates the vector icon in a muted palette: the icon is fully desaturated, its contrast is compressed toward mid-gray, and the result is blended with the effective :disabled text color taken from CSS. The disabled icon therefore visually matches the disabled caption in both light and dark themes, without extra configuration.
  • If the source is SvgImages and ImageIndexDisabled is set, that index is used as is — SVG still honours the current :disabled text color, because the rasteriser paints currentColor from CSS.
  • If the source is SvgImages and ImageIndexDisabled is not set, the alpha-aware desaturation is applied directly to the RGBA raster. Transparent pixels stay transparent, so the disabled icon has no visible box behind it. The AA edges of the icon survive too.
  • If the source is a user Glyph with NumGlyphs ≥ 2, state 1 is used as is — the user already provided a dedicated disabled bitmap, no automatic desaturation is applied.
  • If the source is Glyph with NumGlyphs = 1 or an Images glyph, the bitmap is desaturated on the fly and cached. The same muted tint as above is applied.

Both enabled and disabled variants are cached, so switching Enabled back and forth is free.

CSS Properties

Three additional declarations control the icon's presentation. All three are DPI-aware.

Property Values Notes
glyph-layout left | right | top | bottom Position of the icon relative to the caption. Default: left.
glyph-spacing any CSS length (4px, 0.3em, thin) Gap between the icon and the caption. Scaled by the DPI factor. Default: 4px.
glyph-margin CSS length, or auto / none Offset of the icon+caption group from the content edge. auto uses the CSS padding.

glyph-margin: auto is a special keyword: the control picks the smallest side of the current CSS padding as the margin. This keeps the button visually consistent when only padding is set on TCssButton.

Because TCssBitBtn inherits from TCssButton, every TCssButton rule applies to it as well. The three glyph-* properties are simply added on top, and can be overridden per theme.

Example

css
TCssBitBtn {
  glyph-layout:  left;
  glyph-spacing: 6px;
  glyph-margin:  auto;
}

/* Top-aligned glyphs for toolbar-style buttons. */
TCssBitBtn.toolbar {
  glyph-layout:  top;
  glyph-spacing: 2px;
  text-align:    center;
}
pascal
// SVG icon that recolors itself with the current CSS text color.
// On :disabled the raster is auto-tinted, on :active it is reused
// unless a dedicated pressed icon is provided.
CssBitBtn1.SvgImages         := CssSvgImgList1;
CssBitBtn1.ImageIndex        := CssSvgImgList1.IndexOf('save');
CssBitBtn1.ImageIndexPressed := CssSvgImgList1.IndexOf('save_pressed');
CssBitBtn1.Caption           := 'Save';
CssBitBtn1.UseKindCaption    := False;

// Built-in icon with a custom caption.
CssBitBtn2.Kind            := bkOK;
CssBitBtn2.Caption         := 'Save';
CssBitBtn2.UseKindCaption  := False;

// Bitmap glyph with 4 states (normal / disabled / pressed / down).
CssBitBtn3.Glyph.LoadFromFile('icon_states.png');
CssBitBtn3.NumGlyphs    := 4;
CssBitBtn3.GlyphScaled  := True;

// Glyph from a classic image list.
CssBitBtn4.Images     := ImageList1;
CssBitBtn4.ImageIndex := 3;

Notes and Caveats

  • Assigning a non-empty Glyph (via the Object Inspector or in code) resets Kind to bkCustom, mirroring TBitBtn.
  • Setting a custom Caption when UseKindCaption = True automatically flips it to False — the current Kind's label will no longer overwrite yours. Set UseKindCaption := True again to restore the standard label.
  • The Kind glyphs are drawn with the anti-aliased primitives of the base engine, so their edges are smooth at every DPI. They never use the system font, so no font substitution or missing-glyph issues occur.
  • SvgImages is preferred over Images. To force the classic TImageList for a specific button, just leave SvgImages unset on that button (the property is per-control).
  • The SVG icon is always rendered at the current DPI, because TCssSvgImgList bakes Screen.PixelsPerInch / 96 into its own effective size. Setting GlyphScaled additionally multiplies the size, which is useful only when the same icon must be enlarged on purpose.
  • All glyph bitmaps are cached per state and per DPI factor. ChangeScale regenerates them automatically when the form moves to a monitor with a different scale.
  • When the source is SvgImages, the rendered glyph is kept as a pf32bit bitmap with a real alpha channel, and it is composited per-pixel onto the canvas. This is the only way the anti-aliased edges of the SVG survive on all LCL widgetsets — the TBitmap.TransparentColor-based path used for Images and Glyph would drop the alpha of the edges and produce a jagged outline.
  • In the designer, changing Kind repaints the control immediately, without waiting for the next focus event.

TCssCheckBox

CSS Properties

Property Notes
checkbox-background Fill color of the box.
checkbox-border-color Border color of the box.
checkbox-border-width Border width of the box.
checkbox-radius Corner radius of the box.
check-color Color of the check mark.
checkbox-style toggle / switch turns the box into a pill switch; custom is a synonym with an independently tunable thumb inset.
toggle-width Width of the switch track (default 40 px).
toggle-height Height of the switch track (default 20 px).
toggle-thumb-inset Gap between the thumb and the track edge, per side (default 4 px). Clamped to [0, toggle-height/2 - 1]. Used by both toggle and custom.
toggle-thumb-color Thumb color of the switch (defaults to check-color).
css
TCssCheckBox.toggle {
  checkbox-style: toggle;
  toggle-width: 40px;
  toggle-height: 20px;
  toggle-thumb-inset: 4px;
  checkbox-background: #F1F5F9;
  toggle-thumb-color: #64748B;
}
TCssCheckBox.toggle:checked {
  checkbox-background: #2563EB;
  toggle-thumb-color: #FFFFFF;
}

/* `custom` looks identical to `toggle` unless you override its values —
   which is exactly the point: it lets a theme tune the thumb inset
   for one switch style without affecting the other. */
TCssCheckBox.custom {
  checkbox-style: custom;
  toggle-width: 48px;
  toggle-height: 24px;
  toggle-thumb-inset: 3px;
  checkbox-background: #F1F5F9;
  toggle-thumb-color: #64748B;
}

Pascal Properties

Property Type Default Notes
Checked Boolean False Convenience wrapper over State.
State TCheckBoxState cbUnchecked Tri-state value.
AllowGrayed Boolean False Enables cbGrayed.
ToggleStyle Boolean — Read-only. True when the effective CSS checkbox-style is toggle / switch / custom.
SvgImages TCssSvgImgList nil Per-state SVG source.
ImageIndex Integer -1 Unchecked, normal.
ImageIndexChecked Integer -1 Checked / grayed, normal.
ImageIndexHover Integer -1 Unchecked, hover.
ImageIndexCheckedHover Integer -1 Checked, hover.
ImageIndexFocused Integer -1 Unchecked, focused.
ImageIndexCheckedFocused Integer -1 Checked, focused.
ImageIndexDisabled Integer -1 Unchecked, disabled. When -1, the control auto-generates a muted variant of ImageIndex.
ImageIndexCheckedDisabled Integer -1 Checked, disabled. When -1, auto-generates from ImageIndexChecked.

Per-State SVG Icons

TCssCheckBox can draw its glyph from a TCssSvgImgList instead of the built-in box / switch renderer. As soon as any state resolves to a valid index, the SVG is used for that state; the built-in renderer is skipped. The lookup order matches the CSS state machine — disabled → checked-hover → checked → hover → base — with an extra focus layer between hover and base:

State Index used
Unchecked, normal ImageIndex
Unchecked, hover ImageIndexHover, else ImageIndex
Unchecked, focus ImageIndexFocused, else ImageIndex
Unchecked, disabled ImageIndexDisabled, else auto-tinted ImageIndex
Checked / grayed, normal ImageIndexChecked
Checked / grayed, hover ImageIndexCheckedHover, else ImageIndexChecked
Checked / grayed, focus ImageIndexCheckedFocused, else ImageIndexChecked
Checked / grayed, disabled ImageIndexCheckedDisabled, else auto-tinted ImageIndexChecked

The auto-tinted fallback is the same algorithm used by TCssBitBtn: the enabled artwork is desaturated, its contrast is compressed toward mid-gray, and the result is blended with the effective :disabled text colour from CSS. Transparent pixels stay transparent and the anti-aliased edges survive, so no box appears behind the icon.

pascal
CssCheckBox1.SvgImages                 := CssSvgImgList1;
CssCheckBox1.ImageIndex                := 0;   // unchecked
CssCheckBox1.ImageIndexChecked         := 1;
CssCheckBox1.ImageIndexHover           := 2;
CssCheckBox1.ImageIndexCheckedHover    := 3;
CssCheckBox1.ImageIndexFocused         := 4;
CssCheckBox1.ImageIndexCheckedFocused  := 7;
CssCheckBox1.ImageIndexDisabled        := 6;
CssCheckBox1.ImageIndexCheckedDisabled := 5;

TCssRadioButton

CSS Properties

Property Notes
radio-background Fill color of the circle.
radio-border-color Border color of the circle.
radio-border-width Border width of the circle.
radio-radius Corner radius (when < half of the box).
dot-color Color of the inner dot.
radio-style toggle / switch turns the radio into a pill switch; custom is a synonym with an independently tunable thumb inset.
toggle-width Width of the switch track (default 40 px).
toggle-height Height of the switch track (default 20 px).
toggle-thumb-inset Gap between the thumb and the track edge, per side (default 4 px).
toggle-thumb-color Thumb color of the switch (defaults to dot-color).

Pascal Properties

Property Type Default Notes
Checked Boolean False
ToggleStyle Boolean — Read-only. True when the effective CSS radio-style is toggle / switch / custom.
SvgImages TCssSvgImgList nil Per-state SVG source.
ImageIndex Integer -1 Unchecked, normal.
ImageIndexChecked Integer -1 Checked, normal.
ImageIndexHover Integer -1 Unchecked, hover.
ImageIndexCheckedHover Integer -1 Checked, hover.
ImageIndexFocused Integer -1 Unchecked, focused.
ImageIndexCheckedFocused Integer -1 Checked, focused.
ImageIndexDisabled Integer -1 Unchecked, disabled. -1 falls back to auto-tinted ImageIndex.
ImageIndexCheckedDisabled Integer -1 Checked, disabled. -1 falls back to auto-tinted ImageIndexChecked.

Per-State SVG Icons

Identical to TCssCheckBox, with Checked (a Boolean) replacing TCheckBoxState in the lookup:

State Index used
Unchecked, normal ImageIndex
Unchecked, hover ImageIndexHover, else ImageIndex
Unchecked, focus ImageIndexFocused, else ImageIndex
Unchecked, disabled ImageIndexDisabled, else auto-tinted ImageIndex
Checked, normal ImageIndexChecked
Checked, hover ImageIndexCheckedHover, else ImageIndexChecked
Checked, focus ImageIndexCheckedFocused, else ImageIndexChecked
Checked, disabled ImageIndexCheckedDisabled, else auto-tinted ImageIndexChecked
pascal
CssRadioButton1.SvgImages                 := CssSvgImgList1;
CssRadioButton1.ImageIndex                := 0;
CssRadioButton1.ImageIndexChecked         := 1;
CssRadioButton1.ImageIndexHover           := 2;
CssRadioButton1.ImageIndexCheckedHover    := 3;
CssRadioButton1.ImageIndexFocused         := 4;
CssRadioButton1.ImageIndexCheckedFocused  := 5;
CssRadioButton1.ImageIndexDisabled        := 6;
CssRadioButton1.ImageIndexCheckedDisabled := 7;

TCssCheckGroup / TCssRadioGroup

Both are subclasses of TCssGroupCaptionControl, so they support Caption with CaptionMode (gcmInside, gcmOnBorder) and CaptionBackgroundColor, plus focus-within: true.

They expose CheckBoxCssClass / CheckBoxCssStyle (resp. RadioCssClass / RadioCssStyle) so their children are styled by a separate CSS rule:

css
TCssCheckGroup  { /* ... */ }
.checkbox       { /* applied to the child checkboxes */ }
.toggle         { /* switch appearance for the children */ }
.custom         { /* switch appearance with a different thumb inset */ }

Per-Item SVG Icons

Both groups can push a shared set of SVG icons down to every child control, and let individual children override the unchecked / normal icon:

Property Type Default Notes
SvgImages TCssSvgImgList nil Per-state SVG source for every child.
ItemImageIndexes TStringList — One line per item; each line is the unchecked base index for that item. Empty / -1 falls back to the group-level ImageIndex.
ImageIndex Integer -1 Fallback unchecked index for items not covered by ItemImageIndexes.
ImageIndexChecked Integer -1 Checked index, shared by every child.
ImageIndexHover Integer -1 Unchecked hover index, shared.
ImageIndexCheckedHover Integer -1 Checked hover index, shared.
ImageIndexFocused Integer -1 Unchecked focus index, shared.
ImageIndexCheckedFocused Integer -1 Checked focus index, shared.
ImageIndexDisabled Integer -1 Unchecked disabled index. -1 lets each child auto-tint its own unchecked index.
ImageIndexCheckedDisabled Integer -1 Checked disabled index. -1 lets each child auto-tint.

The group owns SvgImages, so it is enough to assign the list and the shared indexes once — every child receives them through ApplyImagesToChild in both RebuildItems and UpdateChildStyles.

Priority for the unchecked base index:

  1. ItemImageIndexes[Index], when it parses to a value >= 0.
  2. ImageIndex of the group, otherwise.
  3. -1 — the child falls back to whatever it has locally (typically -1).
pascal
CssCheckGroup1.SvgImages                 := CssSvgImgList1;
CssCheckGroup1.ImageIndex                := 0;   // fallback for all items
CssCheckGroup1.ImageIndexChecked         := 1;
CssCheckGroup1.ImageIndexHover           := 2;
CssCheckGroup1.ImageIndexCheckedHover    := 3;
CssCheckGroup1.ImageIndexFocused         := 4;
CssCheckGroup1.ImageIndexCheckedFocused  := 7;
CssCheckGroup1.ImageIndexDisabled        := 6;
CssCheckGroup1.ImageIndexCheckedDisabled := 5;

// Per-item unchecked icons — item 1 and 2 use different artwork:
CssCheckGroup1.ItemImageIndexes.Clear;
CssCheckGroup1.ItemImageIndexes.Add('');       // item 0: falls back to ImageIndex = 0
CssCheckGroup1.ItemImageIndexes.Add('10');     // item 1: custom unchecked index
CssCheckGroup1.ItemImageIndexes.Add('11');     // item 2: custom unchecked index

TCssRadioGroup exposes the same properties under the RadioButtons[Index] accessor (equivalent to CheckBoxes[Index] on TCssCheckGroup).

The group-level CheckBoxCssClass / RadioCssClass ('toggle', 'custom', …) still applies on top of the per-item SVG icons: a child rendered as a switch uses the switch track, and the SVG is drawn over it when its index resolves. In practice you pick one — SVG icons or the built-in renderer — for a given group.

TCssComboBox

TCssComboBox is a dropdown combo box that supports two modes: ccsDropDown (with an embedded editable field) and ccsDropDownList (selection only). It reads its appearance from CSS and exposes child‑control styling for the embedded editor, the popup list box, and the scrollbar.

CSS Properties

Dropdown button

Property Notes
combo-button-background Fill of the dropdown button. Defaults to the control’s background-color, or clBtnFace if that is not set.
combo-button-hover-background Fill of the dropdown button on hover. Defaults to rgb(230,235,240).
combo-button-active-background Fill of the dropdown button while pressed. Defaults to rgb(210,220,230).
combo-button-arrow-color Color of the dropdown triangle. Defaults to the control’s text color.
combo-button-radius Corner radius of the dropdown button. Defaults to 0.
combo-button-border-color Border color of the dropdown button. When omitted, no border is drawn.

Placeholder

These properties affect the placeholder of the combo box itself. In ccsDropDownList the placeholder is drawn by the combo box. In ccsDropDown the placeholder is passed to the embedded TCssEdit and is styled through that editor’s CSS (EditCssClass / EditCssStyle).

Property Notes
placeholder-color Placeholder text color. Defaults to rgb(150,150,150).
placeholder-align left | center | right. Defaults to left.
placeholder-font-weight normal | bold | bolder.
placeholder-font-style normal | italic | oblique.
placeholder-text-decoration underline, line-through, none.

Pascal Properties

Property Type Default Notes
Items TStrings — The list of items.
ItemIndex Integer -1 Index of the selected item.
Text string — In ccsDropDown: the text of the embedded editor. In ccsDropDownList: the text of the selected item.
SelText string — Read‑only. Returns the display text of the selected item.
ComboStyle TCssComboStyle ccsDropDown ccsDropDown or ccsDropDownList.
ReadOnly Boolean False In ccsDropDown it is forwarded to TCssEdit.ReadOnly. In ccsDropDownList it disables opening the dropdown and prevents changing the selected item with the keyboard.
DropDownCount Integer 8 Maximum number of visible rows in the dropdown. Minimum 1.
ItemHeight Integer 18 Height of a list row. Minimum 10.
Placeholder string '' Placeholder text.

Child Controls CSS

Property Default Notes
EditCssClass 'combobox-edit' CSS class of the embedded editor.
EditCssStyle 'background-color: transparent; border: 0px solid transparent; padding: 0px;' Inline style of the embedded editor.
ListBoxCssClass 'combobox-list' CSS class of the dropdown list box.
ListBoxCssStyle '' Inline style of the dropdown list box.
ScrollBarCssClass 'combobox-scrollbar' CSS class of the dropdown scrollbar.
ScrollBarCssStyle '' Inline style of the dropdown scrollbar.

ItemHeight is passed to the internal TCssListBox as the item-height CSS declaration. You can override it per list box by adding item-height to ListBoxCssStyle — declarations in ListBoxCssStyle are appended after the base value, so they take precedence.

Events

Event Notes
OnDropDown Fired before the dropdown is shown.
OnCloseUp Fired after the dropdown is closed.
OnChange Fired when the editor text or the selected item changes.
OnSelect Fired when ItemIndex changes.

Behavior Notes

  • In ccsDropDown an internal TCssEdit is created. The user can type into it, and the text is matched against the items to update ItemIndex.
  • In ccsDropDownList there is no editor. Text returns the display text of the selected item, and Placeholder is drawn by the combo box itself when ItemIndex < 0 and the control is not focused.
  • HtmlMode affects the items in the dropdown (TCssListBox). The embedded editor always has HtmlMode := False, and the closed state shows plain text.
  • When ReadOnly = True and ComboStyle = ccsDropDownList, the dropdown cannot be opened, the up/down keys do not change the selection, and the cursor becomes crDefault.
  • The width of the dropdown button is derived from the content height and is clamped between 12 and 22 scaled pixels.
  • The dropdown list is displayed in a borderless popup form. Its background color is taken from the list box CSS or from the control’s own background-color.

Example

css
TCssComboBox {
  combo-button-background:        #F8FAFC;
  combo-button-hover-background:  #E2E8F0;
  combo-button-active-background: #CBD5E1;
  combo-button-arrow-color:       #0F172A;
  combo-button-radius:            4px;
  combo-button-border-color:      #CBD5E1;

  placeholder-color:              #94A3B8;
  placeholder-align:              left;
  placeholder-font-style:         italic;
  placeholder-text-decoration:    underline;
}
pascal
CssComboBox1.ComboStyle    := ccsDropDownList;
CssComboBox1.ReadOnly      := True;
CssComboBox1.DropDownCount := 10;
CssComboBox1.ItemHeight    := 22;
CssComboBox1.Placeholder   := 'Select...';

CssComboBox1.EditCssClass      := 'combobox-edit';
CssComboBox1.ListBoxCssClass   := 'combobox-list';
CssComboBox1.ScrollBarCssClass := 'combobox-scrollbar';

TCssEdit

Property Notes
placeholder-color Color of the placeholder text.
placeholder-align left | center | right.
placeholder-font-weight normal / bold / numeric.
placeholder-font-style normal / italic / oblique.
placeholder-text-decoration underline, line-through, none.

TCssMemo

Property Notes
selection-background Background of the selection.
selection-color Text color of the selection.
caret-color Caret color.
placeholder-color Color of the placeholder text.
placeholder-align left | center | right.
placeholder-vertical-align top | middle / center | bottom.
placeholder-font-weight normal / bold / numeric.
placeholder-font-style normal / italic / oblique.
placeholder-text-decoration underline, line-through, none.

TCssListBox

Property Notes
item-background Default item background.
item-color Default item text color.
item-hover-background Item background on hover.
item-hover-color Item text color on hover.
item-selected-background Selected item background.
item-selected-color Selected item text color.
item-height Row height.

Plus ScrollBarCssClass / ScrollBarCssStyle for the embedded scrollbar, and the Pascal properties ScrollBarWidth, MouseWheelLines, AlwaysReserveScrollBar.

TCssListView

TCssListView is a CSS-styled replacement for TListView. It supports the same four view styles and the usual item / column model.

pascal
var
  Item: TCssListItem;
begin
  CssListView1.ViewStyle := lvsReport;
  CssListView1.Columns.AddItem('Name', 200);
  CssListView1.Columns.AddItem('Size', 90).Alignment := taRightJustify;
  CssListView1.Columns.AddItem('Modified', 140);

  CssListView1.Items.BeginUpdate;
  try
    Item := CssListView1.Items.AddItem('Report.pdf');
    Item.SubItems.Add('1.2 MB');
    Item.SubItems.Add('2026-10-29');
    Item.ImageIndex := 0;
    Item.Checked := True;
  finally
    CssListView1.Items.EndUpdate;
  end;
end;

Key properties:

Property Notes
ViewStyle lvsReport (default), lvsList, lvsSmallIcon, lvsIcon.
Columns Collection of TCssListColumn (Caption, Width, MinWidth, Alignment, Visible, AutoSize, ImageIndex). Column 0 shows Item.Caption, column n shows Item.SubItems[n - 1].
Items Collection of TCssListItem (Caption, SubItems, ImageIndex, StateIndex, OverlayIndex, Checked, Indent, Data, Selected).
MultiSelect, RowSelect Selection behaviour. Ctrl and Shift are honoured, SelectAll / ClearSelection are available in code.
Checkboxes Draws a checkbox in column 0 (or before the icon in icon views).
ShowColumnHeaders, GridLines, HotTrack, HideSelection Report / interaction options.
ReadOnly When False, F2 or a double click opens the inline editor. Set ReadOnly := True to disable editing.
HtmlMode Renders item captions, subitems, and column headers as HTML (,, <u>, colors, spans). Sorting, incremental search, and AutoSizeColumn use the plain text with tags stripped.
SmallImages, LargeImages, StateImages TCssSvgImgList instances. Small images are used in report / list / small-icon modes, large images in icon mode.
SortType, SortColumn, SortDirection, AutoSort Built-in sorting. AutoSort sorts when a column header is clicked. AlphaSort and CustomSort are also available.
ItemIndex, FocusedColumn, SelCount, Selected, TopItem, VisibleRowCount Runtime selection and scrolling information. In report mode FocusedColumn is the cell that receives the keyboard focus.

Keyboard navigation is spatial and matches the visible item layout:

  • Up/Down move within the current visual column of items.
  • Left/Right move to the same row in the adjacent visual column. If that row does not exist there, the last item of the target column is selected.
  • In lvsReport, Left/Right move the focused header column instead; set ShowItemFocusRect := True to draw the focus rectangle around the current cell.

Events: OnSelectItem, OnItemChecked, OnItemActivate, OnColumnClick, OnCompare, OnInsert, OnDeletion, OnEditing, OnEdited, OnDrawItem, and OnChange.

CSS Properties

Property Notes
header-background Column header background.
header-color Column header text color.
header-hover-background Header background under the mouse when HotTrack is enabled.
header-height Header height in pixels.
header-padding Horizontal header text padding.
sort-marker-color Color of the sort arrow.
item-background Default row / item background.
item-color Default item text color.
item-hover-background, item-hover-color Hover colors.
item-selected-background, item-selected-color Selected item colors.
selection-background, selection-color Fallback selection colors when the item-specific values are not set.
hover-background Fallback hover background.
grid-color, line-color Grid and separator colors.
item-height, row-height Row height.
item-padding, column-padding Horizontal padding inside cells.
scrollbar-size Thickness of the embedded scrollbars.
check-color Check mark color.
placeholder, placeholder-color Text shown when the list is empty.
disabled-background, disabled-color Colors used when Enabled = False.

The embedded scrollbars use ScrollBarCssClass (default listview-scrollbar), the inline editor uses EditCssClass (default listview-edit), and the checkboxes use CheckBoxCssClass (default listview-checkbox checkbox). ScrollBarCssStyle, CheckBoxCssStyle, and CheckBoxInlineCss can be used for per-instance overrides.

TCssProgressBar

TCssProgressBar is a CSS-styled replacement for TProgressBar. It supports both orientations, determinate and indeterminate modes, semantic states, and an optional percentage text — all skinned by the same theme as the rest of the library.

CSS Properties

Property Notes
progress-color Fill color of the bar in the normal state.
progress-error-color Fill color when State = pbsError.
progress-paused-color Fill color when State = pbsPaused.
progress-text-color Color of the percentage text drawn over the fill. When omitted, the control auto-picks black or white based on the luminance of the fill, so the digits stay readable on both light and dark fills.
progress-radius Corner radius of the fill. When omitted, the fill inherits the control's border-radius minus border-width minus padding, so it always tucks neatly inside the track.

Everything else — the track itself — is styled by the ordinary general properties (background-color, border, border-radius, padding, font-*, color).

Pascal Properties

Property Type Default Notes
Min Integer 0 Lower bound of the range.
Max Integer 100 Upper bound.
Position Integer 0 Current value, clamped to [Min..Max]. In pbMarquee mode this value is still the target amplitude of the pulse.
Step Integer 10 Increment used by StepIt.
Orientation TCssProgressBarOrientation pbHorizontal pbHorizontal or pbVertical. In vertical mode the bar fills from the bottom up, and ShowText renders the percentage rotated 90° CCW.
State TCssProgressBarState pbsNormal pbsNormal, pbsError, pbsPaused. Switches the fill color between the corresponding CSS properties.
Style TCssProgressBarStyle pbContinuous pbContinuous (determinate) or pbMarquee (indeterminate).
ShowText Boolean False Draws the current percentage as text on top of the bar. The text is shown in both modes; in pbMarquee it displays the target Position, not the moving pulse.
Smooth Boolean True Animates Position changes over ~250 ms with an ease-out curve instead of jumping. Disable for instant updates.
MarqueeInterval Integer 30 Half-period of the pulse in milliseconds (min. 5).
DisplayFraction Double — Read-only. The fraction (0..1) that is currently drawn, including any in-flight animation.

Methods

Method Notes
StepIt Advances Position by Step.
StepBy(ADelta) Advances Position by an arbitrary value.

Behavior

  • Determinate mode (pbContinuous). Assigning Position triggers a smooth transition from the previous displayed value to the new one (unless Smooth = False), so a StepIt loop reads as a continuously moving bar rather than a series of jumps.
  • Indeterminate mode (pbMarquee). The fill is animated as a pulse: it grows from zero up to the fraction given by Position and back. This makes the bar a useful indicator for long-running operations where the total is unknown but the current stage is meaningful — the amplitude always reflects the caller's latest Position, while the motion itself signals that the process is alive. Changing Position in this mode smoothly retargets the amplitude rather than restarting the pulse.
  • Text color. When ShowText is enabled, the digits are drawn twice, once in color clipped to the track region and once in progress-text-color clipped to the fill region. That is why the same text remains readable whether it sits over the pale track, over the colored fill, or straddles the boundary. If progress-text-color is not set, the control derives it from the fill luminance (black on light fills, white on dark ones).
  • Vertical orientation. pbVertical fills from the bottom up. The percentage text is rendered as a small rotated bitmap, so it works identically on every widgetset — including GTK2, where Font.Orientation is a no-op.
  • Design-time. Animations are intentionally disabled while csDesigning is set, so dropping a TCssProgressBar on a form does not cause a timer to fire inside the IDE.

Example

css
TCssProgressBar {
  background-color: #F1F5F9;
  color:            #1E293B;
  border:           1px solid #E2E8F0;
  border-radius:    8px;
  padding:          2px;

  progress-color:        #2563EB;
  progress-text-color:   #FFFFFF;
  progress-error-color:  #DC2626;
  progress-paused-color: #F59E0B;
}
pascal
// Determinate progress with smooth animation.
CssProgressBar1.Min      := 0;
CssProgressBar1.Max      := 200;
CssProgressBar1.Position := 37;
CssProgressBar1.ShowText := True;

// Vertical, with the percentage rotated 90° CCW.
CssProgressBar2.Orientation := pbVertical;
CssProgressBar2.Position    := 60;
CssProgressBar2.ShowText    := True;

// Indeterminate: pulses from zero up to the current Position and back.
CssProgressBar3.Style       := pbMarquee;
CssProgressBar3.Position    := 75;
CssProgressBar3.ShowText    := True;

// Semantic states override the fill color.
CssProgressBar4.State := pbsError;
CssProgressBar4.State := pbsPaused;

TCssScrollBar

Property Notes
scrolltrack-background Track (groove) background.
thumb-background Thumb fill.
thumb-hover-background Thumb fill on hover.
thumb-active-background Thumb fill while dragging.
thumb-border-color Thumb border.
thumb-radius Thumb corner radius.
arrow-color Arrow color.

TCssSplitter

Property Notes
grip-color Color of the grip dots.
grip-count Number of dots (integer, e.g. 3).
grip-size Size of a dot in px.
grip-spacing Gap between dots in px.

Additional Pascal properties: AutoSnap, SnapThreshold, HighlightAdjacentControls, ResizeStyle (crsUpdate / crsLine).

Menus (TCssMainMenu, TCssPopupMenu, TCssMenuBase)

Property Notes
menu-background Background of the dropdown.
menu-border-color Border of the dropdown.
menu-item-height Height of an item.
menu-text-color Text color of items.
menu-hover-background Item background on hover.
menu-hover-text-color Item text color on hover.
menu-disabled-text-color Text color of disabled items.
menu-separator-color Separator color.
menu-separator-height Height of a separator.
menu-separator-width Width of a separator.
menu-shortcut-color Shortcut text color.

TCssMainMenu additionally supports:

Property Notes
menubar-background Background of the menu bar itself.

Tabs (TCssTabControl, TCssPageControl)

Property Notes
tab-background Background of an inactive tab.
tab-hover-background Background of a hovered tab.
tab-active-background Background of the active tab.
tab-text-color Text color of inactive tabs.
tab-active-text-color Text color of the active tab.
tab-border-color Tab border.
tab-radius Tab corner radius.
tab-spacing Gap between tabs.
tab-padding Horizontal padding inside a tab (used with auto-size).
tab-auto-size true / false / 1 / 0.
tab-max-width Maximum width for auto-sized tabs (0 = unlimited).
tab-cursor Cursor over an inactive tab.
tab-scroll-button-background Fill of the overflow scroll buttons.
tab-scroll-button-hover-background Hover fill of the scroll buttons.
tab-scroll-button-active-background Pressed fill of the scroll buttons.
tab-scroll-button-disabled-background Disabled fill of the scroll buttons.
tab-scroll-arrow-color Arrow color of the scroll buttons.
tab-scroll-arrow-disabled-color Disabled arrow color of the scroll buttons.
tab-scroll-button-radius Corner radius of the scroll buttons.
tab-scroll-button-size Size of a single scroll button (0 = auto).

cursor is applied to the content area (over the active tab and empty space).

TCssVirtualStringTree

Property Notes
header-background Header fill.
header-color Header text color.
header-hover-background Header cell hover fill.
selection-background Selected row fill.
selection-color Selected row text.
hover-background Hovered row fill.
line-color Grid / separator color.
button-color Expand/collapse button color.
checkbox-background Passed to the internal checkbox helpers.
checkbox-border-color Idem.
checkbox-border-width Idem.
checkbox-radius Idem.
check-color Idem.
drop-target-background Highlight of the drop target row.
sort-marker-color Sort arrow color.
disabled-background Background of a disabled row.
disabled-color Text color of a disabled row.
header-height Header height in px.
row-height, item-height Row height in px.
indent Indent per tree level in px.
scrollbar-size Size of the internal scrollbars in px.
header-text-align, header-align left | center | right.
header-vertical-align, header-valign top | middle | bottom.
cell-text-align, row-text-align, node-text-align left | center | right.
cell-vertical-align, row-vertical-align, node-vertical-align top | middle | bottom.
placeholder-color Color of the empty-tree placeholder.
placeholder-align left | center | right.
placeholder-vertical-align top | middle | bottom.
placeholder-font-weight normal / bold / numeric.
placeholder-font-style normal / italic / oblique.
placeholder-text-decoration underline, line-through, none.
html-mode true / false — enables HTML in cells.

Cell icons are not styled through CSS. They are configured through the Pascal properties Images, ShowImages, ImageSize, ImageSpacing, ImageVariant, and the OnGetImageIndex event — see Tree Icons.

Child controls also expose CheckBoxCssClass / CheckBoxCssStyle and EditStyleName for the inline editor.


SVG Image Lists (TCssSvgImgList)

TCssSvgImgList is a non-visual component that plays the same role as TImageList, but stores its pictures as native SVG text. Each image is rasterised on demand, at the size and DPI the caller asks for, using the same anti-aliased primitives that TCssStyledControl uses for its own artwork.

Because SVG is vector-based, a single entry can serve as a 16×16 toolbar icon, a 32×32 button glyph and a 64×64 HiDPI preview without any extra work. TCssSvgImgList is the recommended source for TCssBitBtn, TCssMenuItem and TCssTabControl glyphs when your icons are already shipped as SVG (Illustrator, Inkscape, Figma, Iconify, Material Symbols, and so on).

Why not just use TImageList?

  • Crisp at any DPI. TImageList ships fixed-size bitmaps; on a 200 % monitor they either look tiny or blurry. TCssSvgImgList renders the vector source at the exact device pixel size.
  • One asset, many sizes. No need to author a separate PNG for every DPI bucket.
  • currentColor support. SVG paths that use fill="currentColor" are painted with the effective CSS text color of the consumer (TCssBitBtn, TCssMenuItem and TCssTabControl pass the resolved color automatically). One icon can be a blue "info" bubble in the light theme and a pale yellow one in the dark theme, without two separate files.
  • Per-theme variants. A single entry can carry different SVG bodies for dark, light, high-contrast (or any other variant name), and the consumer picks the right one based on the currently active CSS theme.
  • Smaller .lfm files. SVG text compresses better than raw RGBA bitmaps, especially for line-art icons.

Where It Is Used

  • TCssBitBtn.SvgImages — see TCssBitBtn.
  • TCssMenuItem.SvgImages / TCssMenuBase.SvgImages — see Menus.
  • TCssTabControl.SvgImages / TCssPageControl.SvgImages — see Tabs.
  • TCssVirtualStringTree.Images + OnGetImageIndex — see Tree Icons.
  • Anywhere in your own code that needs a TBitmap from an SVG: TCssSvgImgList.GetBitmap, GetBitmapByName, DrawToCanvas.
  • TCssSvgImgList.AssignToImageList — one-shot migration path: rasterise every SVG entry into a standard TImageList (for legacy controls that still require one). The alpha channel is preserved through a generated mask, so the resulting images keep their transparency.

Supported SVG Subset

The built-in rasteriser is deliberately small but covers what icon sets actually use:

  • Elements: svg, g, path, rect, circle, ellipse, line, polyline, polygon, text, tspan.
  • Path commands: M m L l H h V v C c S s Q q T t A a Z z.
  • Transforms: translate, scale, rotate, skewX, skewY, matrix.
  • Style: fill, fill-opacity, fill-rule, stroke, stroke-opacity, stroke-width, stroke-linecap, stroke-linejoin, stroke-miterlimit, opacity, color (currentColor), style="...", display, visibility.
  • Gradients: linearGradient, radialGradient, including gradientUnits, gradientTransform and spreadMethod. Gradients declared as direct children of <svg> are supported too, not only inside <defs>.
  • Stops: both style="stop-color:...;stop-opacity:..." and the presentation attributes stop-color / stop-opacity are honoured, so output from Illustrator, Inkscape and Figma is parsed correctly out of the box.
  • View box: viewBox + preserveAspectRatio (meet / slice, alignment keywords, none).

Hidden subtrees — elements with display:none or visibility:hidden — are skipped together with their children, so the background rectangles that Illustrator and Inkscape wrap in a hidden <g> never appear in the rasterised output.

Not supported: filters, <use> / <defs> references (other than gradients), <textPath>, CSS animations, embedded fonts. If your icons rely on any of these, pre-render them to PNG or simplify them in the SVG editor.


Menus (TCssMainMenu, TCssPopupMenu)

TCssMainMenu, TCssPopupMenu and their items support icons sourced from a TCssSvgImgList. Icons can be attached both per-item and per-menu, and follow the same theme / variant rules as the rest of the library.

Where the Icon Comes From

Each TCssMenuItem has an SvgImages property, and so does the owning menu (TCssMenuBase.SvgImages). The resolution order at paint time is:

  1. Item-level SvgImages, when set on the specific item — this is the per-item override.
  2. Menu-level SvgImages, when the item does not set its own.
  3. Otherwise the item has no icon.

This means a single SvgImages := CssSvgImgList1 on the menu covers every item; individual items only need their ImageIndex set. When a specific item must use a different source list, set its own SvgImages and it wins for that item.

TCssMenuItem.ImageIndex selects the entry inside the effective source list. -1 (the default) disables the icon for that item.

Menu-wide Presentation

The menu itself controls how the icons are drawn, via three CSS properties on TCssMenuBase:

Property Values Notes
menu-icon-layout left | right Horizontal position of the icon relative to the caption. Default: left.
menu-icon-size CSS length Icon size in device pixels. When omitted, the icon follows the height of the text on the menu canvas.
css
TCssPopupMenu {
  menu-icon-layout: left;
  menu-icon-size:   16px;   /* optional; if omitted, tracks text height */
}

menu-icon-size is DPI-aware: it is scaled by the same factor as the rest of the CSS lengths. When it is not set, the icon follows TextHeight('Mg') on the menu canvas, capped by menu-item-height, so it stays in proportion with the font.

Item-Level CSS (for the icon)

Per-item overrides are not exposed through CSS — they are per-component and per-item. Use the Object Inspector or code:

pascal
MenuItem1.SvgImages  := CssSvgImgList1;
MenuItem1.ImageIndex := CssSvgImgList1.IndexOf('save');

MenuItem2.SvgImages  := CssSvgImgList1;
MenuItem2.ImageIndex := CssSvgImgList1.IndexOf('open');

Layout Rules

  • When at least one visible item in a popup has an icon, a column is reserved for it in every row so captions stay aligned. When no item has an icon, the column is not reserved at all.
  • The check-mark column is reserved only when at least one item is Checked; when nothing is checked, the space is not wasted.
  • The shortcut text and the submenu arrow remain pinned to the right edge. The caption gets exactly the space that is left between the reserved left columns and the reserved right columns.
  • The right-side slot of the item is resolved right-to-left in this order: [pad] [arrow] [shortcut] [right icon when menu-icon-layout=right] [caption]. This is what makes the layout stable under bold HTML text.
  • The menu's own Paint uses the exact same right-margin computation as its measuring pass, so bold HTML captions never wrap to a second line and never get clipped vertically.
  • Top-level items of TCssMainMenu support icons too, with the same resolution rules and the same menu-icon-layout / menu-icon-size CSS properties.

Example

css
TCssPopupMenu {
  menu-background:          #FFFFFF;
  menu-border-color:        #E2E8F0;
  menu-item-height:         26px;
  menu-hover-background:    #2563EB;
  menu-hover-text-color:    #FFFFFF;

  menu-icon-layout:         left;
  menu-icon-size:           16px;

  menu-separator-color:     #E2E8F0;
  menu-shortcut-color:      #94A3B8;
}
pascal
CssPopupMenu1.SvgImages := CssSvgImgList1;

MenuItem1.Caption    := 'Save';
MenuItem1.ImageIndex := CssSvgImgList1.IndexOf('save');

MenuItem2.Caption    := 'Open';
MenuItem2.ImageIndex := CssSvgImgList1.IndexOf('open');

Switching CssStyleProvider1.DefaultStyleName from Light to Dark immediately swaps every menu icon to its dark variant, if the corresponding item defines one.


Tabs (TCssTabControl, TCssPageControl)

Both TCssTabControl and its descendant TCssPageControl support per-tab icons sourced from a TCssSvgImgList.

How to Attach Icons

  • TCssTabControl uses a parallel list of image indexes, TabImageIndexes. Line N of that string list corresponds to tab N; each line is either a number (an index into SvgImages) or -1 / empty for "no icon".
  • TCssPageControl uses the ImageIndex property of each TCssTabSheet. Because pages and tabs are linked one-to-one, TabImageIndexes is derived automatically from the pages — the value of TabImageIndexes is ignored when the control has pages.
pascal
// TCssTabControl — no pages, indexes in a parallel list:
CssTabControl1.SvgImages := CssSvgImgList1;
CssTabControl1.Tabs.Add('Save');
CssTabControl1.Tabs.Add('Load');
CssTabControl1.Tabs.Add('Settings');
CssTabControl1.TabImageIndexes.Add('0');
CssTabControl1.TabImageIndexes.Add('1');
CssTabControl1.TabImageIndexes.Add('2');
pascal
// TCssPageControl — one ImageIndex per page:
CssPageControl1.SvgImages := CssSvgImgList1;
CssPageControl1.Pages[0].ImageIndex := 0;
CssPageControl1.Pages[1].ImageIndex := 1;

Presentation

Two CSS properties on TCssTabControl / TCssPageControl control the icon layout:

Property Values Notes
tab-icon-layout left | right Horizontal position of the icon relative to the caption. Default: left.
tab-icon-size CSS length Icon size in device pixels. When omitted, the icon follows the height of the tab text, capped by the tab height.
css
TCssPageControl {
  tab-icon-layout: left;
  tab-icon-size:   14px;   /* optional; if omitted, tracks text height */
}

tab-icon-size is DPI-aware. When it is not set, the effective icon size is TextHeight('Mg') on the tab canvas, clamped to TabHeight - 6 and to a minimum of 8 px, so it always fits inside the tab.

Layout Rules

  • The icon + caption group is centred inside the tab, whether or not the tab has an icon.
  • When the tab has an icon, the caption is left-aligned inside the remaining space, so the group stays visually balanced.
  • GetTabWidth reserves the same icon column width that Paint uses, so tab widths and icon positions stay in sync. Bold HTML captions are measured against the same width that is later given to them at paint time — nothing gets clipped.
  • TCssPageControl.GetTabImageIndex reads the index from the corresponding TCssTabSheet, so hiding a page via TabVisible automatically hides its icon together with the tab.

Example

css
TCssTabControl,
TCssPageControl {
  tab-background:             transparent;
  tab-hover-background:       #F1F5F9;
  tab-active-background:      #EFF6FF;

  tab-text-color:             #64748B;
  tab-active-text-color:      #1D4ED8;

  tab-icon-layout:            left;
  tab-icon-size:              14px;

  tab-scroll-button-background:            #F1F5F9;
  tab-scroll-button-hover-background:      #E2E8F0;
  tab-scroll-button-active-background:     #CBD5E1;
  tab-scroll-button-disabled-background:   #F8FAFC;
  tab-scroll-arrow-color:                  #475569;
  tab-scroll-arrow-disabled-color:         #CBD5E1;
}

The icon color follows the tab text color for the current state — tab-text-color for inactive / hovered tabs, tab-active-text-color for the active tab. When the CSS theme changes, the icons are re-rasterised with the new color and the new variant.


Tree Icons (TCssVirtualStringTree)

TCssVirtualStringTree can draw an SVG icon in any cell, sourced from a TCssSvgImgList. Icons are requested per-cell through an event, so the same tree can mix different icon sources, show per-row state icons (folder / file, expanded / collapsed, checked / unchecked), or return -1 to leave a cell icon-free.

How to Attach Icons

Assign a TCssSvgImgList to the tree's Images property and handle OnGetImageIndex:

pascal
CssTree1.Images     := CssSvgImgList1;
CssTree1.ShowImages := True;
CssTree1.ImageSize  := 0;    // 0 = follow text height, like menu-icon-size / tab-icon-size

procedure TForm1.CssTree1GetImageIndex(Sender: TObject;
  Node: TCssVirtualNode; Column: Integer; var ImageIndex: Integer);
begin
  if Column = 0 then
    ImageIndex := CssSvgImgList1.IndexOf(NodeFolderOrFile(Node))
  else
    ImageIndex := -1;       // no icons in other columns
end;

The event fires once per cell on every paint, so it is also a convenient place to compute state-dependent icons. For example, return the index of folder-open for expanded folders and of folder for collapsed ones.

Pascal Properties

Property Type Default Notes
Images TCssSvgImgList nil Source of the per-cell icons. No icons are drawn when unset.
ShowImages Boolean True Master switch. When False, OnGetImageIndex is not called and the icon slot is not reserved.
ImageSize Integer 0 Icon size in design-time pixels; 0 means "follow the current font", like menu-icon-size / tab-icon-size.
ImageSpacing Integer 4 Gap between the icon and the cell text, in design-time pixels.
ImageVariant string '' SVG variant name passed to TCssSvgImgList.GetBitmap. When empty, the variant is derived from the tree's StyleName / StyleProvider.DefaultStyleName, so theme switches propagate automatically.
OnGetImageIndex TCssGetImageIndexEvent nil Called for every visible cell; return the index inside Images, or -1 for "no icon".

Sizing and DPI

  • When ImageSize = 0, the icon follows the same rule as the menu and tab icons: the effective size is Canvas.TextHeight('Mg') on the tree canvas (with the CSS font already applied), capped by the "native" size of the TCssSvgImgList entry (GetEffectiveWidth / GetEffectiveHeight), and clamped to FItemHeight - ScalePx(2). This keeps the icon visually in proportion with the cell text and prevents tiny SVG assets from being blown up.
  • When ImageSize > 0, that value is scaled by the tree's DPI factor (ScalePx). This mirrors menu-icon-size and tab-icon-size.
  • TCssSvgImgList itself is DPI-aware, so SVG artwork is rasterised at the exact device pixel size — sharp at 100 %, 125 %, 150 %, 200 % and higher.

Layout Rules

  • The icon is placed between the leading decorations and the caption: after the expand/collapse button and the checkbox column on the first column, or at the cell's left edge on subsequent columns.
  • The icon is vertically centred in the row ((FItemHeight - iconHeight) div 2).
  • The cell text is shifted to the right by iconWidth + ImageSpacing so the caption never overlaps the icon.
  • The tree reserves space for the icon only in rows that actually request one via OnGetImageIndex. Rows with ImageIndex = -1 keep the caption aligned to the same left edge they would have without icons — icons are not forced into a fixed column the way they are in a menu.
  • AutoSizeColumns accounts for the icon width of the first cell when computing the column size, so calling it after loading a dataset with icons gives correct widths.

Theming and Disabled State

  • currentColor tinting. The icon is rasterised with the effective text color of the cell as ACurrentColor, so SVG paths written as fill="currentColor" follow the row's text color — the :hover color on a hovered row, the selection-color on a selected row, and the disabled-color on a disabled row.
  • Per-theme variants. If the SVG entry carries a dark variant (see Themes / Variants under TCssSvgImgList), it is picked automatically when the CSS theme changes — as long as ImageVariant is left empty. Setting ImageVariant explicitly overrides this and forces a fixed variant.
  • Disabled rows. IsNodeDisabled already paints the row in disabled-color; the icon picks up the same color and becomes visually muted without any extra code.

Example — Folder / File Icons with State

pascal
procedure TForm1.TreeGetImageIndex(Sender: TObject;
  Node: TCssVirtualNode; Column: Integer; var ImageIndex: Integer);
begin
  if Column <> 0 then
  begin
    ImageIndex := -1;
    Exit;
  end;

  if cvsHasChildren in Node.States then
  begin
    if cvsExpanded in Node.States then
      ImageIndex := CssSvgImgList1.IndexOf('folder-open')
    else
      ImageIndex := CssSvgImgList1.IndexOf('folder');
  end
  else
    ImageIndex := CssSvgImgList1.IndexOf('file');
end;
css
/* Nothing tree-specific is required: the icon follows the CSS text color
   of the row. Selection and disabled colors are picked up automatically. */
TCssVirtualStringTree {
  selection-color: #FFFFFF;
  disabled-color:  #94A3B8;
}

Switching CssStyleProvider1.DefaultStyleName from light to dark changes the icon tint immediately, and — if the SVG entry has a dark variant — swaps the artwork as well, together with the rest of the tree.

Notes and Caveats

  • The tree does not own the TCssSvgImgList: freeing the list while it is still assigned to Images is safe (the tree is notified via FreeNotification and clears the reference), but icons disappear once the list is gone.
  • Icon indexes are validated on every paint: an index that is < 0 or >= Images.Count is treated as "no icon".
  • Disabled nodes never draw a hover or selected background, so the icon tint naturally follows disabled-color only. There is no per-node icon override — use OnGetImageIndex to return different indexes for cvsDisabled nodes if you want a dedicated "disabled" artwork.

Themes / Variants

Every item can carry multiple SVG bodies — one per theme. Variants are stored as VariantName=Svg text pairs inside the item's Variants string list, and are looked up case-insensitively, so dark in the list will match Dark coming from TCssStyleProvider.DefaultStyleName.

Resolution rules:

  1. The item first tries the exact variant name it was asked for.
  2. If that variant is not defined on the item — or its body is empty — the item falls back to the base Svg property.
  3. If the caller does not pass a variant name at all, the base Svg is used.

The variant name is passed in by the consumer:

  • TCssBitBtn, TCssMenuItem, TCssTabControl pass StyleName (if set) or StyleProvider.DefaultStyleName of the owning control, so switching the theme switches the icons together with the rest of the UI.
  • Standalone users can pass it explicitly: CssSvgImgList1.GetBitmap(0, 32, 32, clBlack, 'dark').
  • If there is no CSS at all, the fallback is TCssSvgImgList.DefaultVariant (empty by default).

Pascal API

Member Description
Items Collection of TCssSvgImgListItem (Name, Svg, Variants).
Count Number of items (read-only).
Width, Height Design-time size of a single cell, in pixels (default 16 × 16).
Scaled If True (default), Width and Height are multiplied by Screen.PixelsPerInch / 96.
DefaultVariant Fallback variant name used by parameterless GetBitmap / DrawToCanvas.
AddSvg(Name, Svg) Appends an item and returns its index.
AddSvgFromFile(Name, FileName) Appends an item, loading SVG text from a file.
Delete(Index), Clear Removes one item / all items.
IndexOf(Name) Case-insensitive lookup by Name, or -1.
GetSvg(Index) / GetSvg(Name) Returns the base SVG text.
SetSvg(Index, Value) Replaces the base SVG text of an item.
GetParseError(Index) Returns the error string if the last parse failed, or an empty string.
GetEffectiveWidth / GetEffectiveHeight Width / height after applying Scaled.
GetVariantNames Returns a sorted, de-duplicated list of every variant name used by any item. Caller owns the list.
HasVariant(Name) True when at least one item defines the given variant.
GetBitmap(Index, W, H, CurrentColor) Rasterises with the list's DefaultVariant. Caller owns the bitmap.
GetBitmap(Index, W, H, CurrentColor, Variant) Same, with an explicit variant.
GetBitmapByName(Name, W, H, CurrentColor [, Variant]) Same, looked up by Name.
DrawToCanvas(Canvas, Index, X, Y [, W, H, CurrentColor]) Rasterises at the effective size (or an explicit one) and blits to the canvas.
AssignToImageList(ImageList, CurrentColor) Rasterises every entry into a standard TImageList, generating the transparency mask.
SaveToStream / LoadFromStream Batch serialisation of all items.
SaveToFile / LoadFromFile Same, from a file on disk.

TCssSvgImgListItem

Property Notes
Name Logical name. Used by IndexOf, GetBitmapByName, and shown in the visual editor.
Svg Base SVG text of the image — used when no variant matches.
Variants TStrings in VariantName=SVG text format. Assign or edit through the visual editor.
Image Read-only. Returns the parsed TSvgImage for the base Svg (lazily created).

Example — Using It From Code

pascal
// Load an icon at design time (see "Design-Time Support" below)
// or from code:
CssSvgImgList1.AddSvgFromFile('check',  'icons/check.svg');
CssSvgImgList1.AddSvgFromFile('cancel', 'icons/cancel.svg');

// Attach it to a button:
CssBitBtn1.SvgImages  := CssSvgImgList1;
CssBitBtn1.ImageIndex := CssSvgImgList1.IndexOf('check');

// Or rasterise manually — the caller owns the returned bitmap:
Bmp := CssSvgImgList1.GetBitmap(CssSvgImgList1.IndexOf('check'), 64, 64, clGreen);
try
  Image1.Picture.Assign(Bmp);
finally
  Bmp.Free;
end;

Example — Recolouring with currentColor

Use currentColor inside the SVG wherever a design tool would normally hard-code a palette color:

xml
<svg viewBox="0 0 16 16">
  <path fill="currentColor" d="M8 1.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13z"/>
</svg>

The icon then follows the effective CSS text color of the consumer. TCssBitBtn passes GetEffectiveTextColor, TCssMenuItem and TCssTabControl pass the menu / tab text color, so the same SVG becomes green on a normal button, muted grey on a :disabled button, and white on a dark theme — all without a single extra file.

Example — Per-Theme Variants

pascal
CssSvgImgList1.Items[0].Name := 'check';
CssSvgImgList1.Items[0].Svg  := '...light-theme SVG...';
CssSvgImgList1.Items[0].Variants.Values['dark'] := '...dark-theme SVG...';

Then just switch the CSS theme on the provider:

pascal
CssStyleProvider1.DefaultStyleName := 'dark';

Every consumer that points to this list (TCssBitBtn, TCssMenuItem, TCssTabControl) will pick the dark variant of check automatically.

Performance Notes

  • The rasteriser flattens Bézier and arc segments into polygons once, then runs the anti-aliased coverage pass over them. Everything is done in memory; there is no disk cache.
  • TCssSvgImgList keeps an internal LRU cache of already-rasterised bitmaps keyed by (index, width, height, currentColor, variant, serial). The cache is invalidated whenever an item's Svg or Variants change, a new item is added, or Width / Height / Scaled / DefaultVariant change.
  • For icon sets of a few dozen entries the cost is negligible. If you ship thousands of SVGs, load them on demand rather than all at once.
  • TCssBitBtn keeps its own small per-state cache of the scaled glyph, so re-painting a button does not re-rasterise the SVG on every frame. The cache is keyed on the resolved variant too, so switching the theme only re-rasterises once.

Notes and Caveats

  • The SVG parser is not an XML validator. If it fails, Image.Error is non-empty and the rasteriser returns a blank bitmap. Call GetParseError(Index) if you need to surface the reason.
  • The rasteriser produces a pf32bit bitmap with a real per-pixel alpha channel — no TransparentColor chroma-keying anywhere, so semi-transparent strokes and antialiased edges blend correctly on any background.
  • Small icons (16–64 px) are rendered at up to 4× the requested size and box-filtered down with premultiplied alpha. The extra cost is paid once per (image, size, colour, variant) tuple and cached; repeated draws reuse the cached bitmap. Icons larger than 256 px are rasterised at their native size.
  • The Variants lookup is case-insensitive, which matters because TCssStyleProvider.DefaultStyleName is often capitalised (Dark, Light) while authors type variant names in lowercase.

Themes (Light and Dark)

The library ships with a ready-made theme, Themes/ModernTheme.css, which contains both variants and covers every control. Load it into a TCssStyleProvider:

pascal
CssStyleProvider1.LoadFromFile('ModernTheme.css');
CssStyleProvider1.DefaultStyleName := 'light';   // or 'dark'

The @variant markers partition the file:

css
@variant light
{
  .standart-form { background-color: #ffffff; color: #000000; }
  .standart-btn  { background: #eeeeee; font-weight: bold; }
  .standart-edit { background: #f9f9f9; text-align: left; }
}

/* Global rules shared by both variants */
*       { font-family: 'Segoe UI', sans-serif; font-size: 10pt; color: #1E293B; }
::hint  { /* ... */ }
a       { /* ... */ }
TCssButton { /* ... */ }
/* ... all other controls ... */

@variant dark
{
  .standart-form { background-color: #1E293B; color: #ffffff; }
  .standart-btn  { background: #333333; color: #cccccc; }
  .standart-edit { background: #222222; color: #ffffff; }
}

/* The same set of rules again, overriding the light palette */
*       { color: #E2E8F0; background-color: #1E293B; }
/* ... */

Switching the whole application between variants is one line:

pascal
procedure TForm1.SetDarkTheme;
begin
  CssStyleProvider1.DefaultStyleName := 'dark';
end;

All controls that reference the provider (and any TCssProxy on the form) are restyled automatically.


HTML Formatting

Every TCssStyledControl supports HTML rendering. Enable it with:

pascal
CssLabel1.HtmlMode := True;
CssLabel1.Caption  := '<b>Hello</b>, <i>world</i>!';

HtmlMode affects the rendering of Caption in:

  • TCssButton, TCssCheckBox, TCssRadioButton, TCssLabel, TCssPanel, TCssGroupBox
  • TCssEdit (placeholder only), TCssMemo (placeholder only)
  • TCssComboBox (list items)
  • TCssListBox (items)
  • TCssCheckGroup, TCssRadioGroup (item captions)
  • TCssMainMenu, TCssPopupMenu (item captions)
  • TCssTabControl / TCssPageControl (tab captions)
  • TCssVirtualStringTree (cell text, when OnGetText returns HTML)
  • TCssStyledHintWindow (tooltips, if HintHtmlMode is True)

Supported Tags

Tag Effect
<h1> … <h6> Heading (bold, larger font).
,, <section>, <article>, <header>, <footer> Block element.
`` Centered block.
<pre>, <xmp> Preformatted monospace block (line breaks preserved).
`` Horizontal rule.
`
` Line break.
<q> Adds « and » around the text.
, Bold.
, Italic.
<u>, <ins> Underline.
<del>, <s>, <strike> Strike-through.
<code>, ``, <samp>, <tt> Monospace font.
`` Superscript.
`` Subscript.
`` Text color.
`` Inline style (see below).
<ul>, <ol>, <li> Bullet / numbered list.
<a href="..."> Link (see below).

Supported Inline Styles

Only a small subset of CSS is supported inside style="...":

  • color
  • font-weight
  • font-style
  • text-decoration

Block Attributes

  • align="left|center|right" on ,, <h1> … <h6>, ``.
  • style="text-align: ..." on the same elements.

Entities

The parser decodes: &nbsp;, &lt;, &gt;, &quot;, &#39;, &amp;.

Measuring

All controls measure HTML using the same parser, so AutoSize and layout are correct. Use MeasureHtmlTextSize if you need to know the size programmatically.

Use HtmlToPlainText to strip HTML from a string:

pascal
Plain := CssLabel1.HtmlToPlainText('<b>Hello</b> <i>world</i>');  // "Hello world"

Link Handling

HTML text can contain links:

pascal
CssLabel1.HtmlMode := True;
CssLabel1.Caption  := 'Visit <a href="https://example.com">our site</a>.';

CssLabel1.OnLinkClick := @LabelLinkClick;

procedure TForm1.LabelLinkClick(Sender: TObject; const AHref, AText: string);
begin
  ShellExecute(0, 'open', PChar(AHref), nil, nil, SW_SHOWNORMAL);
end;

Links are highlighted according to the a, a:hover, a:active rules:

css
a        { color: #2563EB; text-decoration: underline; cursor: pointer; }
a:hover  { color: #1D4ED8; }
a:active { color: #1E40AF; }

The cursor automatically becomes crHandPoint (or whatever the CSS cursor says) when the mouse is over a link.

You can also query the link at a point programmatically:

pascal
var Href, Text: string;
if CssLabel1.TryGetLinkAt(Point(X, Y), Href, Text) then
  Memo1.Lines.Add(Href + ' -> ' + Text);

CssLabel1.ClickLinkAtPoint(Point(X, Y));

Tooltips with CSS and HTML

Set ShowHint := True, Hint := '...' and (optionally) HintHtmlMode := True. Then style the hint with the ::hint selector:

css
::hint {
  background-color: #F8FAFC;
  color: #0F172A;
  border: 1px solid #94A3B8;
  border-radius: 6px;
  padding: 6px 10px;
  font-size: 9pt;
  text-align: left;
  vertical-align: top;
}

If HintHtmlMode is True, the hint text is rendered as HTML; otherwise it is plain text.

The hint is drawn by an internal TCssStyledHintWindow that uses the same CSS engine, so all general properties (background, border, radius, padding, font, shadow) are supported.

Message Dialogs (CssMessageDialogs)

The CssMessageDialogs unit provides CSS-styled replacements for the standard dialog functions from Dialogs.pas. Instead of the native LCL dialogs, it opens a TCssMessageForm — a plain TForm used only as a container — whose entire visible content is built from TCssStyledControl descendants:

  • a TCssPanel backdrop that draws the dialog background, border and rounded corners from CSS;
  • a TCssMessageIcon that renders the standard information / warning / error / question icon using the anti-aliased primitives of the base engine (no bitmap assets), or — when an icon index is supplied — a vector icon taken from a shared TCssSvgImgList;
  • a TCssLabel for the message text, optionally rendered as HTML;
  • an optional TCssEdit for text and password input;
  • an optional one-pixel TCssPanel divider between the icon and the message;
  • TCssButton instances for the action buttons, inheriting :default and :cancel pseudo-classes from TCssButton.

Because a TForm is not a TCssStyledControl, its Color property is kept in sync with the active CSS theme through a TCssProxy attached to the form. The backdrop panel uses the same .msgdialog class name as the form proxy, so both resolve to the same declaration and blend cleanly at the rounded corners.

Screenshots

Three representative dialogs, each shown in both the light and the dark variant of the bundled theme. Every element visible inside the dialog — the backdrop, the icon column, the divider, the HTML label, the input edit, and the action buttons — is a TCssStyledControl and is skinned by the same TCssStyleProvider that skins the rest of the application.

Confirmation dialog — mixed HTML, custom SVG icon

A warning dialog that combines a heading, an unordered list, and a colored paragraph. The icon is taken from the application-wide TCssSvgImgList through CssMessageDlgIcon(..., 22, ...).

Light Dark

Information dialog — release-notes layout

A pure-HTML dialog with headings, bold / italic / underline / strikeout fragments, inline colors, code fragments, ordered and unordered lists, a horizontal rule, and a centered footer. No SVG icon is used here — the built-in anti-aliased information glyph is drawn from the base engine.

Light Dark

Input dialog — HTML prompt with an embedded TCssEdit

An input dialog built through CssInputBox. The prompt itself is HTML (and a colored), and the embedded TCssEdit uses the ordinary TCssEdit styling of the active theme.

Light Dark

Function Mapping

Standard (Dialogs.pas) CSS replacement
ShowMessage CssShowMessage
ShowMessageFmt CssShowMessageFmt
MessageDlg CssMessageDlg / CssMessageDlgIcon
MessageDlgPos CssMessageDlgPos / CssMessageDlgPosIcon
MessageBox CssMessageBox / CssMessageBoxIcon
InputBox CssInputBox
InputQuery CssInputQuery
PasswordBox CssPasswordBox

The *Icon variants take an extra AIconIndex argument that selects an entry inside the application-wide icon source; see Custom SVG Icons below.

The Css prefix avoids a name clash with the Dialogs unit. If you prefer the original names, alias them in a one-line wrapper unit.

Quick Start

  1. Add CssMessageDialogs to the uses clause of the unit where you show dialogs.
  2. (Optional) Place a TCssStyleProvider on your main form and load a theme into it. Every dialog will find that provider automatically, because it is looked up by walking the active form's components.
  3. Call any of the functions:
pascal
CssShowMessage('Operation completed.');
CssMessageDlg('Save changes?', mtConfirmation, [mbYes, mbNo, mbCancel], 0);

If there is no provider on the active form and no global provider has been set, the dialog still opens and uses system default colours.

Two optional global setters configure behaviour that applies to every subsequent dialog. Call them once, typically in the application's .lpr file right after the main form is created — see the demo for the exact ordering — or, if you prefer, in the main form's FormCreate:

pascal
// Handle clicks on <a href="..."> links inside dialog messages.
CssMessageDlgSetOnLinkClick(@HandleDialogLink);

// Use a shared SVG list for dialog icons.
CssMessageDlgSetIconSource(CssSvgImgList1);

Pass nil to either setter to clear the setting. See Link Handling and Custom SVG Icons below for the full API.

Function Overloads

CssMessageDlg and CssMessageDlgPos come in four overloads each, and CssMessageBox in two:

pascal
// 1. No caption, automatic default button
CssMessageDlg('Message', mtInformation, [mbOK], 0);

// 2. Explicit default button, no caption
CssMessageDlg('Message', mtInformation, [mbYes, mbNo], 0, mbNo);

// 3. Explicit caption, automatic default button
CssMessageDlg('Message', mtWarning, [mbRetry, mbCancel], 0, 'Network error');

// 4. Explicit caption and default button
CssMessageDlg('Delete permanently?', mtError, [mbYes, mbNo, mbCancel], 0,
              'Confirmation', mbCancel);

CssMessageDlgPos takes the same four combinations plus a screen position:

pascal
CssMessageDlgPos('Message', mtInformation, [mbOK], 0, 200, 150);
CssMessageDlgPos('Message', mtInformation, [mbOK], 0, 200, 150, 'Title');
CssMessageDlgPos('Message', mtInformation, [mbOK], 0, 200, 150, mbCancel);
CssMessageDlgPos('Message', mtInformation, [mbOK], 0, 200, 150, 'Title', mbCancel);

CssMessageBox accepts either the WinAPI flag set alone, or the flag set plus an explicit TMsgDlgBtn that overrides MB_DEFBUTTONn:

pascal
CssMessageBox('Caption', 'Message', MB_OK or MB_ICONINFORMATION);
CssMessageBox('Caption', 'Overwrite?', MB_YESNOCANCEL or MB_ICONWARNING, mbCancel);

CssInputBox, CssInputQuery, and CssPasswordBox keep the standard signatures:

pascal
S  := CssInputBox('Name', 'Enter display name:', 'Anonymous');
Ok := CssInputQuery('Path', 'Export directory:', S);
S  := CssPasswordBox('Auth', 'Password:');

Custom SVG Icons

By default a dialog draws one of the four built-in icons — information, warning, error, or question — using the anti-aliased primitives of the base engine. To show your own artwork instead, point the dialog engine at a TCssSvgImgList and pick an entry by index.

The icon source is application-wide: set it once, and every dialog that is asked to display an SVG icon uses the same list. The index is a per-call argument, so different dialogs can show different pictures without duplicating the source.

pascal
// Once, at application startup.
CssMessageDlgSetIconSource(CssSvgImgList1);

// Later — every dialog that wants an SVG icon passes its own index:
CssMessageDlgIcon(
  'The file was <b>saved</b>.',
  mtInformation, [mbOK], 0,
  5);                                    // 5 = index inside CssSvgImgList1

The index is passed after all existing optional parameters, which keeps FPC's overload resolution unambiguous: Integer and TMsgDlgBtn are numerically compatible, and putting the index last is what lets the new signatures coexist with the old ones.

API

Function Notes
CssMessageDlgIcon Four overloads, mirroring CssMessageDlg.
CssMessageDlgPosIcon Four overloads, mirroring CssMessageDlgPos.
CssMessageBoxIcon Two overloads, mirroring CssMessageBox.
pascal
// Minimal form: just an icon index.
CssMessageDlgIcon('Message', mtInformation, [mbOK], 0, 5);

// Index + default button.
CssMessageDlgIcon('Save?', mtConfirmation, [mbYes, mbNo], 0, 7, mbNo);

// Index + caption.
CssMessageDlgIcon('Message', mtInformation, [mbOK], 0, 5, 'Update');

// Full form.
CssMessageDlgIcon('Delete?', mtError, [mbYes, mbNo, mbCancel], 0,
                  12, 'Confirmation', mbCancel);

// Positioned variant.
CssMessageDlgPosIcon('Message', mtInformation, [mbOK], 0,
                     200, 150, 5);

// WinAPI-style variant.
CssMessageBoxIcon('Caption', 'Overwrite?',
                  MB_YESNOCANCEL or MB_ICONWARNING, 3, mbCancel);

Behaviour

  • A valid index replaces the built-in shape. When 0 <= AIconIndex < Source.Count, the SVG is drawn inside the icon column and the icon column is reserved even for mtCustom dialogs that would otherwise be icon-less.
  • An invalid index or a nil source falls back silently. AIconIndex = -1 (the default in all CssMessageDlg* wrappers) and an out-of-range index both mean "use the built-in shape".
  • The icon follows the active theme. The variant name passed to TCssSvgImgList.GetBitmap is the dialog's StyleName, which is itself derived from the provider's DefaultStyleName. If the SVG entry defines a dark variant, switching the CSS theme switches the dialog icon too, without any extra code.
  • currentColor works. The rasteriser is passed GetEffectiveTextColor, so SVG paths written as fill="currentColor" follow the dialog's color declaration from .msgdialog.
  • Transparency is preserved. The rendered glyph is a pf32bit bitmap with a real per-pixel alpha channel; it is composited per-pixel onto the dialog backdrop. Anti-aliased edges and semi-transparent artwork blend correctly against both the light and the dark variant of the theme.
  • The icon background is painted by the dialog. TCssMessageIcon does not call the base class's Paint; instead it fills its client rectangle with the backdrop's styled background color and then draws the SVG on top. This is what removes the grey block that a naive inherited Paint would leave around the icon on themed panels.

Per-Dialog Override

CssMessageDlgSetIconSource changes the source for every subsequent dialog. To use a different source — or no source at all — for a single dialog, use TCssMessageForm.CreateMessageDialog directly:

pascal
var
  Dlg: TCssMessageForm;
begin
  Dlg := TCssMessageForm.CreateMessageDialog(
    Application.MainForm, mtInformation,
    'Choose an option.', [mbOK], mbOK,
    False, False, '',
    'Title',
    nil,              // OnLinkClick: nil = use the global handler
    7);               // AIconIndex
  try
    Dlg.ShowModal;
  finally
    Dlg.Free;
  end;
end;

Link Handling

When CssMessageDlgSetHtmlMode(True) is active, a dialog message may contain <a href="..."> links. Links are rendered with the CSS rules for a, a:hover, and a:active, and clicking one raises a callback with the href and the plain-text body of the link.

Two ways to install the handler:

  1. Globally, once at application startup, for every dialog:
    pascal
    CssMessageDlgSetOnLinkClick(@HandleDialogLink);
    
    procedure TForm1.HandleDialogLink(Sender: TObject;
      const AHref, AText: string);
    begin
      if AHref = 'help' then
        OpenHelp(AText)
      else
        ShellExecute(0, 'open', PChar(AHref), nil, nil, SW_SHOWNORMAL);
    end;

The same four CssMessageDlgSet* calls can equally well be placed in the application's .lpr file, right after Application.CreateForm(TForm1, Form1); — that is where the bundled demo puts them, so the configuration is already active before the main form is first shown.

  1. Per dialog, when you construct a TCssMessageForm yourself:

    pascal
    Dlg := TCssMessageForm.CreateMessageDialog(
      Application.MainForm, mtInformation,
      'Read the <a href="https://example.com">documentation</a>.',
      [mbOK], mbOK,
      False, False, '',
      'Welcome',
      @HandleDialogLink);   // per-dialog handler

    The same handler can also be set after construction:

    pascal
    Dlg.OnLinkClick := @HandleDialogLink;

If both a per-dialog handler and a global handler are present, the per-dialog one wins. If neither is set, links are still drawn with the correct style and still change the cursor, but clicking them does nothing.

Callback signature

pascal
type
  TCssLinkClickEvent = procedure(Sender: TObject;
    const AHref, AText: AnsiString) of object;

The Sender passed to the callback is the TCssMessageForm itself, not the internal TCssLabel. This is deliberate: the dialog is the public object the caller interacts with, and it stays valid as long as the dialog is open.

Caveats

  • Avoid opening another CssMessageDlg-based dialog from inside the link-click handler if the global handler is set. Doing so would recurse: the new dialog would use the same global handler, and a click on a link in it would open yet another dialog. Open the target with ShellExecute, OpenDocument, or your own navigation logic instead.
  • Turning HTML mode off (CssMessageDlgSetHtmlMode(False)) also disables link rendering, because the <a> tags are no longer parsed.

Caption Handling

If the caption is empty, Application.Title is used — this matches the LCL behaviour of the standard MessageDlg. CssShowMessage and CssShowMessageFmt always use this default caption.

Default Button Selection

When a TMsgDlgBtn value is passed explicitly, it becomes the default only if that button is actually present in the Buttons set. Otherwise the default is auto-picked by the same priority order used by LCL:

pascal
OK > Yes > YesToAll > No > NoToAll > Retry > Abort > Ignore > All > Close

The default button is rendered through TCssButton:default, the cancel-like button through TCssButton:cancel. The Escape key activates the first Cancel-like button found, and Return activates the default button.

Provider Resolution

The style provider for a dialog is resolved in this order:

  1. The first TCssStyleProvider found on the currently active form, or on Application.MainForm if there is no active form.
  2. The provider set globally with CssMessageDlgSetStyleProvider.
  3. No provider — controls fall back to system default colours.
pascal
// App-wide fallback, used when the owning form has no provider of its own.
CssMessageDlgSetStyleProvider(CssStyleProvider1);
CssMessageDlgSetStyleProvider(CssStyleProvider1, 'dark');   // with a style name
CssMessageDlgSetStyleProvider(nil);                          // reset

HTML Mode

HTML rendering of the message text is off by default, so < and > inside normal strings are treated as ordinary characters. Enable it globally:

pascal
CssMessageDlgSetHtmlMode(True);
try
  CssShowMessage('<b>Success!</b><br>The file has been saved.');
  CssMessageDlg('<h3>Summary</h3><ul>' +
                '<li>3 files updated</li>' +
                '<li>1 file deleted</li>' +
                '</ul>',
                mtInformation, [mbOK], 0, 'Results');
finally
  CssMessageDlgSetHtmlMode(False);
end;

When HTML mode is off, #13#10, #13, and #10 in the message produce line breaks, and long lines wrap automatically to the dialog width. When HTML mode is on, use for a line break and ... for a paragraph — #13#10 is treated as a space, as in HTML.

The same flag controls HTML rendering in the prompt of CssInputQuery / CssInputBox / CssPasswordBox.

CSS Styling

The dialog is skinned by a single .msgdialog rule (applied to both the form proxy and the backdrop panel), plus its helper rules for the divider and the icon:

css
.msgdialog {
  background-color: #FFFFFF;
  color: #1E293B;
  border: 1px solid #CBD5E1;
  border-radius: 10px;
  padding: 0px;
  focus-within: false;
}
.msgdialog:hover,
.msgdialog:focus { border-color: #CBD5E1; }

/* One-pixel divider between the icon column and the message text. */
.msgdialog-divider {
  background-color: #E2E8F0;
  border: none;
  border-radius: 0px;
  padding: 0px;
  focus-within: false;
}
.msgdialog-divider:hover,
.msgdialog-divider:focus { border: none; }

/* Icon canvas: transparent, no border, no padding. */
TCssMessageIcon {
  background-color: transparent;
  border: none;
  border-radius: 0px;
  padding: 0px;
}

Everything else — buttons, input edit, label, and any SVG icon — is styled by the ordinary TCssButton, TCssEdit, and TCssLabel rules of the active theme. This means a TCssMessageForm inherits the same look as the rest of the application: no separate dialog theme to maintain.

An SVG icon is rasterised with currentColor bound to the dialog's color, so SVG artwork that uses fill="currentColor" follows the theme in the same way the built-in shapes do. In dark mode the icon becomes light, and vice versa.

.msgdialog deliberately sets focus-within: false to suppress the base TCssPanel focus ring; the dialog is not a composite widget that needs a group highlight.

Layout

The dialog lays itself out in a single column:

pascal
????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????
??? [PadPx]                                      ???
??? [PadPx][icon][PadPx][div][PadPx][text ...][PadPx]
???                                              ???
???                    [input edit]              ???
???                                              ???
???                     [btn][btn][btn]          ???
??? [PadPx]                                      ???
????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????
  • The icon is vertically centred against the whole message block.
  • For single-line messages, the icon and the text share a vertical centre, so the row reads as one unit.
  • For multi-line messages, the text anchors to the top and the icon floats in the middle of the block, matching the behaviour of the standard Windows MessageBox.
  • The divider (when present) is centred within the same block, with a small top and bottom inset so it does not touch the panel edges.
  • Buttons are right-aligned; their width, height and gap are DPI-scaled.

DPI

Every metric — padding, spacing, icon size, edit height, button size and gap — is scaled through the standard ScaleDpi helper, so the dialog looks correct at 100 %, 125 %, 150 %, 200 % and higher.

Full Example

pascal
uses
  ..., CssMessageDialogs;

procedure TForm1.RunDialogDemos;
var
  R: Integer;
  S: string;
  Ok: Boolean;
begin
  // Plain message.
  CssShowMessage('A simple information box.');

  // Formatted message.
  CssShowMessageFmt('File "%s" not found in "%s".',
                    ['settings.ini', 'C:\ProgramData']);

  // Confirmation, default button = No.
  R := CssMessageDlg('Save changes?',
    mtConfirmation, [mbYes, mbNo, mbCancel], 0, mbNo);

  // Destructive action, caption and safe default.
  R := CssMessageDlgPos('Delete permanently?',
    mtError, [mbYes, mbNo, mbCancel], 0, 300, 200,
    'Confirmation', mbCancel);

  // WinAPI-style.
  R := CssMessageBox('Caption', 'Overwrite the existing file?',
    MB_YESNOCANCEL or MB_ICONWARNING, mbCancel);

  // Input.
  S  := CssInputBox('Name', 'Enter display name:', 'Anonymous');
  Ok := CssInputQuery('Path', 'Choose an export directory:', S);
  S  := CssPasswordBox('Auth', 'Enter administrator password:');

  // Custom SVG icon — index 5 inside the global source.
  CssMessageDlgIcon('The file was saved.', mtInformation, [mbOK], 0, 5);

  // SVG icon with a caption.
  CssMessageDlgIcon('Delete this entry?', mtConfirmation,
                    [mbYes, mbNo], 0, 12, 'Confirmation');

  // HTML link with the global handler installed above.
  CssMessageDlg(
    'See the <a href="https://example.com/release-notes">release notes</a> ' +
    'for details.',
    mtInformation, [mbOK], 0);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  CssMessageDlgSetHtmlMode(True);
  CssMessageDlgSetStyleProvider(CssStyleProvider1);
  CssMessageDlgSetOnLinkClick(@HandleDialogLink);
  CssMessageDlgSetIconSource(CssSvgImgList1);
end;

procedure TForm1.HandleDialogLink(Sender: TObject;
  const AHref, AText: string);
begin
  ShellExecute(0, 'open', PChar(AHref), nil, nil, SW_SHOWNORMAL);
end;

Design Notes

  • The dialog window is a plain TForm used only as a container. Every visible element is a TCssStyledControl.
  • TCssMessageIcon deliberately does not call inherited Paint. The base class fills the control with GetParentBackgroundColor, which silently falls back to clBtnFace when a CSS lookup fails at paint time; on a themed backdrop that produces an ugly grey block around the icon. Instead, the dialog passes the backdrop's styled background to the icon through SetExternalBackgroundColor, and the icon fills its own client area with that color before compositing the SVG.
  • The per-dialog handler for links is stored on the form and forwarded to the internal label via a small adapter method, so external code never sees the internal TCssLabel. This keeps the public API stable if the label is ever replaced by another renderer.
  • The form's Color is synchronised with the CSS theme through a TCssProxy attached to the form, using the same .msgdialog selector as the backdrop panel. The two never diverge, so rounded corners never show a mismatched corner pixel.
  • Icons are drawn with the AA primitives (DrawAntiAliasedCircle, DrawAntiAliasedTriangle) from the base engine — no external images, no bitmap assets, no missing-glyph issues.
  • HTML mode, the style provider, and the input password mask are global state. Call CssMessageDlgSetHtmlMode and CssMessageDlgSetStyleProvider once at application startup, not per dialog.
  • Because the dialog is modal and blocks the calling thread, it behaves exactly like the standard MessageDlg: code after the call runs once the user has pressed a button.

Style Provider API

TCssStyleProvider is a non-visual component that holds the CSS text and notifies all registered controls when it changes.

Key Properties

Property Description
CssText Full CSS text (may contain @variant blocks).
FileName Path to a .css file — loaded automatically on assignment.
DefaultStyleName Variant used by controls whose StyleName is empty.
StyleCount Number of loaded variants (read-only).
StyleNames[Index] Name of a variant (read-only).
CssByName[Name] Get/set the CSS text of a single variant.
OnChange Fired after the provider's content changes.

Key Methods

pascal
CssStyleProvider1.Clear;
CssStyleProvider1.AddOrUpdateStyle('light', 'TCssButton { ... }');
CssStyleProvider1.RemoveStyle('dark');
CssStyleProvider1.HasStyle('light');                  // Boolean
CssStyleProvider1.GetCss('light');                    // string
CssStyleProvider1.SetCss('light', '...');
CssStyleProvider1.GetCssForControl('');               // effective CSS for the default variant
CssStyleProvider1.LoadFromFile('theme.css');
CssStyleProvider1.LoadFromStrings(Memo1.Lines);
CssStyleProvider1.LoadFromCss('TCssButton { ... }');
CssStyleProvider1.SaveToFile('theme.css');
CssStyleProvider1.RegisterControl(CssButton1);
CssStyleProvider1.UnRegisterControl(CssButton1);

Switching Themes at Runtime

pascal
procedure TForm1.SetDarkTheme;
begin
  CssStyleProvider1.DefaultStyleName := 'dark';
end;

procedure TForm1.SetLightTheme;
begin
  CssStyleProvider1.DefaultStyleName := 'light';
end;

All controls that reference the provider are restyled automatically.


Proxy Styling for Standard LCL Controls

TCssProxy is a non-visual component that reads the currently active CSS variant from a TCssStyleProvider and applies a curated set of declarations to plain LCL controls — the ones that do not descend from TCssStyledControl.

How It Works

For each component owned by the same form, TCssProxy picks a CSS class by component type:

Component type Property Default class
TCustomForm FormStyleName .standart-form
TCustomButton ButtonStyleName .standart-btn
TCustomEdit EditStyleName .standart-edit
TCustomLabel LabelStyleName — (unset)
TCustomPanel PanelStyleName — (unset)
anything else DefaultStyleName — (unset)

For the matched class, TCssProxy extracts the rule body from the active variant (or from the provider's fallback) and applies:

  • background / background-color → Color (and turns off Transparent if present),
  • color → Font.Color,
  • font-family → Font.Name (via ResolveFontFamily),
  • font-size → Font.Size,
  • font-weight → Font.Style + [fsBold] (or -),
  • font-style → Font.Style + [fsItalic] (or -),
  • text-align → Alignment.

Everything else in the rule is ignored.

Typical Use

Drop a TCssStyleProvider and a TCssProxy onto the form, wire them up, and configure the class names in the Object Inspector:

pascal
CssProxy1.ThemeProvider    := CssStyleProvider1;
CssProxy1.FormStyleName    := 'standart-form';
CssProxy1.ButtonStyleName  := 'standart-btn';
CssProxy1.EditStyleName    := 'standart-edit';
CssProxy1.LabelStyleName   := 'standart-label';
CssProxy1.PanelStyleName   := 'standart-panel';

Then in CSS:

css
.standart-form { background-color: #ffffff; color: #000000; }
.standart-btn  { background: #eeeeee; font-weight: bold; }
.standart-edit { background: #f9f9f9; text-align: left; }

Whenever the active variant changes, TCssProxy re-applies its rules. The application of styles is deferred via Application.QueueAsyncCall to avoid doing layout work while the provider is still updating.


High DPI Support

The library is High DPI aware. All pixel values that come from CSS are rescaled automatically when the DPI of the monitor changes, so the same theme looks equally sharp at 100 %, 125 %, 150 %, 200 % and higher.

What is scaled

  • All length values parsed from CSS: padding, border-width, border-radius, box-shadow offsets/blur/spread, text-shadow offsets, font-size when given in px, toggle-width / toggle-height, checkbox-radius / radio-radius, grip-size / grip-spacing, tab-spacing / tab-padding / tab-scroll-button-size, item-height / row-height / header-height, scrollbar-size / thumb-radius, indent, etc.
  • Hard-coded UI metrics inside the controls: check mark thickness, drop-down arrow size, splitter grip dots, menu separators and item paddings, tab strip margins, scroll-button arrows, tree expand/collapse buttons, cell text insets, placeholder insets, and so on.

What is not scaled

  • Every TCssSvgImgList entry is rasterised at the current DPI, so SVG icons are always sharp at 100 %, 125 %, 150 %, 200 % and higher. TCssSvgImgList.Scaled (default True) controls whether the design-time cell size is multiplied by the DPI factor — leave it at True for the same behaviour as the rest of the library. TCssBitBtn, TCssMenuItem and TCssTabControl additionally treat icon sizes as DPI-aware lengths (see menu-icon-size, tab-icon-size) and re-rasterise on ChangeScale.
  • Colors, fonts by name, font-weight, font-style, text-decoration, text-align, vertical-align, cursor, opacity, box-shadow color and all other non-length values — they are device-independent by nature.
  • Sizes and positions of controls that are managed by LCL itself (Align, Anchors, Constraints, Width / Height set in the Object Inspector). Those are already scaled by the LCL layout engine when Application.Scaled := True is set (the default for new projects).
  • Font.Size in points — LCL scales it internally; the library leaves it untouched.

How it works

  • Each TCssStyledControl keeps an internal ScaleFactor derived from Font.PixelsPerInch (falling back to Screen.PixelsPerInch, then to 96). At 96 DPI the factor is 1.0; at 144 DPI it is 1.5; and so on.
  • When the LCL calls ChangeScale on the control (which happens when the form is moved to a monitor with a different DPI, or when Application.Scaled is toggled), the control refreshes its ScaleFactor and re-applies the effective CSS. No action is required from application code.
  • The native Windows title bar managed by TCssFormDarkTitle also follows the theme on DPI change; on Linux and macOS it is a no-op.

What you need to do in your application

Nothing, as long as the project follows the standard LCL conventions:

  1. Application.Scaled := True; — this is already the case in the demo project (CssStyledControlsDemo.lpr) and in any new Lazarus project created with the default settings.
  2. Provide High DPI manifests for Windows, if you want crisp non-client areas. The Lazarus IDE has a Project → Project Options → Application → Use manifest resource option; selecting the standard DPI-aware manifest is enough. The library itself does not ship a manifest.

If you need the current scale factor in your own code, use:

pascal
var
  S: Double;
begin
  S := CssButton1.ScaleFactor;   // 1.0 at 96 DPI, 1.5 at 144 DPI, 2.0 at 192 DPI
end;

Manual scaling helper

Descendants and helper classes that live outside the TCssStyledControl hierarchy (for example, the popup window used by TCssMainMenu and TCssPopupMenu) can convert a design-time pixel value to device pixels through the menu they belong to:

pascal
Scaled := FMenu.ScaleForDpi(8);   // 8 design-time pixels -> device pixels

ScaleForDpi is a public method on every TCssStyledControl; internally it multiplies its argument by the control's current ScaleFactor.


Native Form Title Bar (Windows)

CssFormDarkTitle.pas (shipped with the library) can drive the native Windows 10/11 title bar of a TForm so that it follows the active CSS variant. On Linux and macOS every method is a safe no-op, so the unit compiles and links without any Windows-specific dependencies on those platforms, and calling it unconditionally is fine.

Quick Start

The simplest way is to turn on automatic attachment once, after the main form has been created:

pascal
uses
  ..., CssFormDarkTitle;

// In the .lpr file:
Application.Initialize;
Application.CreateForm(TForm1, Form1);

// Every form that becomes visible from now on — including forms
// created later — will follow the active CSS variant.
TCssFormDarkTitle.EnableAutoAttach(Form1.CssStyleProvider1);

Application.Run;

After that, switching the theme in code —

pascal
CssStyleProvider1.DefaultStyleName := 'dark';

— automatically repaints the native title bar of every attached form.

Attaching a Single Form

If you only want a specific form to follow the theme, use AttachForm:

pascal
TCssFormDarkTitle.AttachForm(Form1, CssStyleProvider1);

AttachForm is idempotent: calling it twice on the same form is safe.

Attaching Every Existing Form

If you prefer explicit control over which forms are attached — and you do not want new forms to be picked up automatically — use AttachAllForms:

pascal
Application.CreateForm(TForm1, Form1);
Application.CreateForm(TForm2, Form2);
Application.CreateForm(TForm3, Form3);

TCssFormDarkTitle.AttachAllForms(Form1.CssStyleProvider1);

Companion methods:

pascal
TCssFormDarkTitle.RefreshAllForms;
TCssFormDarkTitle.DetachAllForms;

AttachAllForms does not subscribe to newly created forms. If a form is created after this call, attach it explicitly with AttachForm, or switch to EnableAutoAttach instead.

Automatic Attachment

EnableAutoAttach installs a handler on Screen.AddHandlerFormVisibleChanged, so every form in the application is attached the first time it becomes visible:

pascal
TCssFormDarkTitle.EnableAutoAttach(CssStyleProvider1);

Notes:

  • Forms that already exist are attached immediately.
  • Forms created later are attached when they are first shown.
  • Calling EnableAutoAttach again replaces the previous provider.
  • DisableAutoAttach stops the automatic behavior; forms that are already attached remain attached. Call DetachAllForms afterwards if you also want to detach them.
  • IsAutoAttachEnabled returns True while automatic attachment is active.

Custom Variant Names

By default, any variant whose name contains dark (case-insensitive) is treated as a dark theme. If your variants are named differently, pass an explicit list:

pascal
TCssFormDarkTitle.AttachForm(Form1, CssStyleProvider1, ['night', 'black']);
TCssFormDarkTitle.AttachAllForms(CssStyleProvider1, ['night', 'black']);
TCssFormDarkTitle.EnableAutoAttach(CssStyleProvider1, ['night', 'black']);

Applying the System Preference

To match the current OS preference at startup (dark or light title bar based on the user's Windows settings):

pascal

[...]