Flowforge Logo
4.x
Essentials

Design System

The tokens, hooks and states a Modern board exposes for styling, and the rules that keep them stable.

A Modern board is styled through a public contract: CSS variables, class hooks and data attributes. Everything on this page is public API. Anything not listed here is internal and can change in any release.

The contract applies to Modern boards only. Classic boards keep their flowforge-* classes and are not affected by anything below.

Layering

Flowforge ships plain CSS. It contains no Tailwind utilities and no @apply, so your app needs no @source line and a Tailwind prefix() cannot break it.

The stylesheet opens with the layer order Filament uses, and puts every rule in one sublayer:

@layer properties, theme, base, components, utilities;

@layer components.flowforge {
    /* every rule */
}

Your unlayered CSS always beats flowforge's. Your layered CSS wins when it sits in components itself or in a later layer such as utilities.

Filament's own component styles sit in components, which outranks the components.flowforge sublayer. Flowforge uses !important in the few places it must beat them. See Properties set with !important.

Filament panels get the stylesheet in the page head. A standalone board links it in its own markup. You do not register or import it.

Variables

Flowforge never sets a public --ff-* variable. Each rule reads the variable through a private alias with a default, so a value you set at any scope wins. That scope can be :root, .ff-board or one card.

The dark column shows where the default changes under Filament's .dark class. A single value applies to both modes.

VariableTierDefault (light / dark)SinceUsed for
--ff-surfacesemanticvar(--gray-50) / var(--gray-950)4.2.0Background of the sticky column-header strip in the lane view. Set it to the page background.
--ff-surface-sunkensemanticvar(--gray-100) / color-mix(in oklab, #fff 5%, transparent)4.2.0Background of a column tray and of a lane cell. --ff-tray-bg overrides it.
--ff-surface-raisedsemantic#fff / var(--gray-900)4.2.0Background of a card and of the filters panel. --ff-card-bg overrides it for cards.
--ff-bordersemanticcolor-mix(in oklab, var(--gray-950) 5%, transparent) / color-mix(in oklab, #fff 10%, transparent)4.2.0Ring around a card and around the filters panel. --ff-card-ring overrides it for cards.
--ff-textsemanticvar(--gray-950) / #fff4.2.0Column titles, lane titles, card titles and the add row on hover.
--ff-text-mutedsemanticvar(--gray-500) / var(--gray-400)4.2.0Secondary text and icons: the group-by label, column icons, counts, summaries, tags, card meta, the add row, the avatar overflow, lane counts and icons, and the header subheading.
--ff-accentsemanticvar(--primary-500)4.2.0Outline of an accepting drop target, the drop gap border and the loading text.
--ff-accent-tintsemanticvar(--primary-50) / color-mix(in oklab, var(--primary-500) 10%, transparent)4.2.0Fill of a tray or lane cell that accepts the card.
--ff-dangersemanticvar(--danger-500)4.2.0Outline of a refusing tray or lane cell, and the limit hint.
--ff-warningsemanticvar(--warning-600) / var(--warning-400)4.2.0Count of a full column.
--ff-radiussemantic0.75rem4.2.0Card radius. The tray and lane cell radius is this plus 0.25rem.
--ff-gapsemantic1.25rem4.2.0Space between columns, on the flat board and in the lane grid.
--ff-easesemanticcubic-bezier(0.16, 1, 0.3, 1)4.2.0Easing of tray and cell fills, add row color, card shadow and card action fades.
--ff-durationsemantic200ms4.2.0Duration of the same transitions.
--ff-column-widthcomponent300px4.2.0Width of a column, and of each column in the lane grid.
--ff-tray-bgcomponentvar(--ff-surface-sunken)4.2.0Background of a column tray and of a lane cell.
--ff-tray-radiuscomponentcalc(var(--ff-radius) + 0.25rem)4.2.0Corner radius of a tray and of a lane cell.
--ff-card-bgcomponentvar(--ff-surface-raised)4.2.0Card background.
--ff-card-ringcomponentvar(--ff-border)4.2.0Card ring color.
--ff-card-radiuscomponentvar(--ff-radius)4.2.0Card corner radius.
--ff-card-shadowcomponent0 1px 2px 0 rgb(0 0 0 / 0.05)4.2.0Card shadow list. It sets the resting and the hover shadow together.
--ff-card-paddingcomponent0.75rem4.2.0Space inside a card.
--ff-drag-tiltcomponent2.5deg4.2.0Rotation of the card while it is dragged.
--ff-drag-shadowcomponent0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)4.2.0Shadow list of the dragged card.
--ff-placeholder-bordercomponentvar(--ff-accent)4.2.0Border of the drop gap.

Flowforge also reads the Filament palette: --gray-*, --primary-*, --danger-* and --warning-*. It never sets them.

Notes that apply to several variables:

  • The shadow variables take a shadow list. Use 0 0 #0000 for no shadow, never none. Flowforge combines the value with a ring, and none makes the whole declaration invalid.
  • --ff-surface paints the sticky lane-header strip behind the column headers. Set it to the background your page uses, so cards scroll under the strip without showing through.
  • --ff-card-shadow sets the resting shadow and the hover shadow together.
  • The dragged card is a clone on body. Flowforge copies the variables above from the card you picked up, so a value set at any scope reaches the clone.

Hooks

Hooks are class names. Modern markup carries ff-* hooks only. The flowforge-* classes of classic boards never appear in Modern views, so a rule written for one board does not reach the other.

HookElement
ff-boardThe board root
ff-columnsThe row that holds the columns of a board without lanes. Set its padding to space the columns from the page edge
ff-columnOne column: its header and its tray. In the lane view it holds only the column header
ff-column-headerThe row with the title, count, summary and actions
ff-countThe card count in a column header
ff-summaryThe column summary text
ff-trayThe surface that holds a column's cards. The lane view has no tray, its cells are the surface
ff-cardOne card
ff-card-actionsThe action menu in a card's corner
ff-add-rowThe add-card row, the "Show more cards" button in a lane cell and the "Show more lanes" button
ff-lanesThe root of the lane view
ff-lane-headerThe header of one lane. It stays at the left edge while the lanes scroll sideways, and a click collapses or expands the lane
ff-cellOne lane and column intersection, a drop target
ff-limit-hintThe hint shown over a full column's tray during a drag. The lane view does not render it
ff-card-tagOne tag from CardTag
ff-card-metaThe row of entries from CardMeta
ff-card-avatarOne avatar from CardAvatar

Other ff-* classes exist for layout. They are internal.

States

Flowforge reports state through data attributes. SortableJS toggles classes only, so flowforge-modern.js mirrors its states into these attributes.

AttributeOnMeaning
data-draggingThe dragged card cloneA drag is in progress
data-placeholderThe drop gapWhere the card will land
data-drop="active" or data-drop="blocked"A column, or a lane cellThe hovered target accepts or refuses the card
data-fullA columnThe column is at its limit
data-collapsedA column, or a lane headerThe column shows as a collapsed rail, or the lane's cells are hidden
data-pendingA cardIts move is saving

The dragged clone sits on body, outside .ff-board. A rule that must reach it starts at .ff-card[data-dragging], and the visible card is its .ff-card-body child.

Customizing

Set variables in your theme and add hook rules beside them:

:root { --ff-accent: var(--primary-600); --ff-radius: .5rem; --ff-drag-tilt: 1.5deg; }
.dark { --ff-card-bg: var(--gray-900); }
.ff-column[data-drop="blocked"] .ff-tray { outline: 2px dashed var(--ff-danger); }

The first line sets three variables for every board. The second sets a dark mode value. The third is a hook rule: it outlines a column that refuses the card you are dragging.

A variable set on .ff-board applies to that board only. A variable set on one card applies to that card only.

Tailwind apps

Tailwind v4 apps can map variables to utilities with @theme inline:

@theme inline {
    --color-ff-surface: var(--ff-surface, var(--gray-50));
    --color-ff-accent: var(--ff-accent, var(--primary-500));
}

Flowforge never sets --ff-* variables, so each mapped variable needs the fallback shown, which matches the light default in the variable table. Set the variable under .dark for a dark value. inline keeps each utility reading the variable at use time, so your overrides still apply.

Card components

CardTitle, CardTag, CardMeta and CardAvatar are styled by the Modern stylesheet and designed for Modern boards. On a classic board they render ff-card-* markup, and their look is not guaranteed: after a wire:navigate visit to a Modern board the Modern stylesheet stays in the page head and can style them. See Card Components.

Properties set with !important

Flowforge sets these properties with !important, to beat Filament component styles that outrank its sublayer. To override one, put your own !important rule in an earlier layer, because cascade layers reverse the order for !important. An unlayered or later-layer !important rule loses to flowforge's layered one.

@layer base {
    .ff-card-meta .fi-badge { color: red !important; }
}
SelectorProperties
.ff-card-meta .fi-badgepadding, font-weight, color, background, box-shadow
.ff-card-meta .fi-in-text-itemfont-size, color
.ff-filtersoverflow, background-color, box-shadow
.ff-filters-toolbarpadding-inline
.ff-filters-panel--aboveborder-bottom-width
.ff-filters-panel--before, .ff-filters-panel--afterbackground-color, border-radius, box-shadow
.ff-filters-indicatorspadding-inline

The .ff-filters* classes are internal. They are listed because they carry !important, and they can change in any release.

Versioning

  • Adding a variable, hook or state is a minor release.
  • Renaming or removing one happens in a major release only. The old name keeps working for that major through a fallback, var(--ff-new, var(--ff-old)), and this page marks it deprecated.

The Since column in the variable table records the release that added each variable.

Copyright © 2026