@agilecontent/ui — React Primitives
@agilecontent/ui is the shared component library that everything in
the React shell (apps/cms) and every federated remote build on top of.
It bundles a curated set of UI primitives — form fields, layout shells,
tables, modals, navigation — plus the theme tokens and icons that keep
the rebrandable surface consistent across customers.
Concretely, it's an Ant Design wrapper. Most exports are either:
- A re-export of an Ant Design component, unchanged, so the whole
shell agrees on the same antd version (
Alert,Tooltip,Typography,Card, …). - A thin wrapper on top of an Ant Design control, adding shell-wide
behavior — color theming, read-only mode, async data loading, our
form-item conventions (
Select,Input,DatePicker, …). - A pure agilecontent primitive with no antd counterpart
(
PageLayout,Sidebar,TopBar,Account,Tour,EPG).
The package lives at
packages/ui
inside the MibFrontEnd monorepo and is published as @agilecontent/ui.
Federated remotes pin it as a shared dependency in their Module
Federation config so the version the shell loads is the version the
widget uses — no double-bundling antd into a customer remote.
Related docs.
Component Authoring — how a federated React widget plugs into the shell. The widgets use@agilecontent/uiprimitives instead of importingantddirectly.
Theming — the CSS custom variables this library reads.
Built-in Components — the stock React widgets, all built from these primitives.
Local Development — how to run the shell + a remote together for live development.
Why a wrapper, not antd directly
Three reasons we don't let federated widgets import antd directly:
- One antd version per shell. With Module Federation,
@agilecontent/uiis shared (deduped). If a customer remote importsantddirectly with its own version, you get two antd runtimes loaded on the same page — broken modals, duplicateConfigProviders, doubled style sheets. - A single rebranding surface. Every primitive reads the same CSS
custom variables defined at
:root. A customer can change the brand color in one place and every widget — built-in or custom — picks it up. See Theming. - Project conventions baked in. Several wrappers add behavior the
shell expects:
SelecthonoursreadOnly,Inputsupports our AdmField permission flags,Formintegrates with our save lifecycle. Going around the wrapper means re-implementing those conventions per widget.
Installation in a federated remote
If you started from the hands-on tutorial's federated remote
or mini-agiletv it's
already wired. For a fresh remote:
// remote/package.json
"dependencies": {
"@agilecontent/ui": "^2.21.0",
"@agilecontent/mib-api-connector": "^x.y.z",
"@agilecontent/mib-modules": "^x.y.z",
"react": ">=18.0.0",
"react-dom": ">=18.0.0"
}
// remote/vite.config.ts
federation({
name: 'customComponents',
filename: 'remoteEntry.js',
exposes: { './all': './src/main.tsx' },
shared: [
'react', 'react-dom',
'@agilecontent/ui', // ← share, do not duplicate
'@agilecontent/mib-api-connector',
'@agilecontent/mib-modules',
'react-i18next', 'styled-components', 'swr',
],
})
The shell already lists the same packages as shared, so they're loaded once across the shell + every remote.
What's exported
The full surface lives in
packages/ui/src/index.tsx.
Below is the practical roll-up grouped by purpose.
Layout shells
| Export | Purpose |
|---|---|
PageLayout |
The full page chrome — sidebar + topbar + content slot. Most full-screen federated widgets render inside this. |
Sidebar |
Left-rail menu. Reads MenuItemConfig[]. |
TopBar |
The branded header with logo, notifications, account. |
Account |
The user dropdown shown in the top-right. |
Breadcrumb |
Path crumbs at the top of a sub-page. |
ComponentLayout |
The card frame each page-component renders inside (title, toolbar slot, body, footer). Use this so your custom widget visually matches the built-in ones. |
Card, Drawer, Modal, PopUpBox, Popover |
Standard surfaces. Modal exposes confirm, error, info, warning, plus the useModal hook. |
Tabs, Collapse, Steps |
Containers for hierarchical or multi-step content. |
Col, Row, Grid |
Flex-grid primitives. |
Divider, Space |
Spacing primitives. |
Skeleton, SkeletonInput, FormSkeleton, TableSkeleton, Spin |
Loading states. |
Result |
Standard "success / error / 404" page result. |
Empty |
"No data" placeholder. |
Form controls
The form-item wrappers are the most-used part of the library — they're how your widget renders a single editable field. They all read shell context (theme, locale, permissions) and forward unknown props through to the underlying antd component.
| Export | Underlying antd | Notes |
|---|---|---|
Form, FormProvider, Field, FieldGroup, FormItemConfigContext, getOperatorFunction |
antd/Form |
Our form is wrapped to integrate with the shell's ContentEdition save lifecycle. Field is the equivalent of Form.Item plus our AdmField conventions. |
Input |
antd/Input |
Adds our standard read-only / disabled handling. |
TextArea |
antd/Input.TextArea |
Same. |
InputNumber |
antd/InputNumber |
Same. |
Select |
antd/Select |
Adds colors prop for color-tagged options, readOnly mode. |
AsyncSelect |
antd/Select |
Async option-loading with debounced search. |
AsyncTree, TreeSelect, AsyncTreeSelect |
antd/TreeSelect |
Hierarchical pickers; the Async variants lazy-load branches. |
AutoComplete, AsyncAutoComplete |
antd/AutoComplete |
Free-text with suggestions. |
DatePicker, TimePicker, RangePicker |
antd/DatePicker.* |
All wrapped to use shell locale + timezone (see Localization). |
Switch |
antd/Switch |
Standard. |
Radio, RadioGroup |
antd/Radio |
Plus RadioChangeEvent re-export. |
Checkbox |
antd/Checkbox (re-export) |
Standard. |
CronScheduler |
react-js-cron based |
Cron expression editor used by Job-Workflow style pages. |
JsonEditor |
Monaco-based | JSON editor with validation. |
EditorField |
Monaco-based | Generic code editor for blob fields. |
Upload, UploadFile, RcFile, UploadChangeParam |
antd/Upload |
File upload widget. |
SelectableSearch |
custom | The simple-search "field selector + input" used by the List component header. |
Data display
| Export | Purpose |
|---|---|
Table, TableTypes, DefaultImage |
The tabular widget all list/related-list components render through. Heavy customisation surface — TableTypes.RenderType covers Source/Boolean/Date/Image/Enum/Link cell renderers. |
NestedTable |
Tables-inside-tables (expanded row contents). |
SortableList, SortableListTypes |
Drag-to-reorder list, used by the Ordered Related List. |
List, ListItem, ListItemMeta |
antd/List re-export — for plain bullet lists, not the data List Component. |
Tree |
antd/Tree re-export — full-tree display. |
Tag, Badge |
Pill / chip primitives. |
Avatar, AvatarGroup |
User/source avatars. |
Image |
Lightbox-capable image. |
Progress |
Bar / circle progress. |
FieldBox |
A read-only "label + value" pair, used by Form when a field is read-only. |
Pagination
| Export | Purpose |
|---|---|
AdvancedPagination |
Page number + page-size + total count. Used at the bottom of List / Related List. |
BasicPagination |
Just prev/next + page number. |
Navigation & feedback
| Export | Purpose |
|---|---|
Tour, TourProps |
Step-by-step on-page guides (the new-feature tutorials). |
notification |
Top-right toast notifications. |
successMessage, errorMessage, infoMessage, loadingMessage, message |
Imperative toast API used across the shell. |
Alert |
Inline banner. |
Tooltip |
Standard. |
Popconfirm |
Inline confirm-on-click. |
Buttons
@agilecontent/ui ships four brand-styled button variants — all from
the same ./components/buttons export:
| Export | When to use |
|---|---|
PrimaryButton |
The page's main action (e.g. Save, Submit). Solid background, brand color. |
SecondaryButton |
Important but non-primary actions (Cancel, Reset). Bordered. |
LinkButton |
Inline navigational action that looks like a link. |
TextButton |
Minimal text-only action (e.g. for toolbar items). |
Plus Button (the plain antd one) is re-exported for fully custom cases.
Icons
The whole @ant-design/icons set is re-exported via the /icons subpath:
import { PlusOutlined, EditOutlined, SaveOutlined } from '@agilecontent/ui/icons';
This way every widget uses the same icon set without re-bundling it.
There's also an Icon wrapper at the root that adds project styling
conventions.
Theming
| Export | Purpose |
|---|---|
UIProvider |
The root provider that supplies theme + locale + AntD ConfigProvider. The shell already wraps the whole app in it; federated widgets don't need to wrap themselves. |
defaultTheme, Theme, DynamicTheme |
The default theme tokens (colors, typography, spacings). For a per-customer rebrand override the CSS variables instead — see Theming. |
GlobalStyle |
Global CSS reset + base styles. The shell injects this once. |
ConfigProvider |
Antd's ConfigProvider re-export. |
Charts and special-purpose subpaths
Three subpath exports keep the main bundle small. Import only what you need:
// Charts
import { LineChart, BarChart, ... } from '@agilecontent/ui/chart';
// EPG primitives (used by the EPG built-in component)
import { Scheduler, Track, ... } from '@agilecontent/ui/epg';
// Icons (already mentioned)
import { PlusOutlined, ... } from '@agilecontent/ui/icons';
The chart subpath is a wrapper around the chart library; the EPG
subpath is the timetable-style scheduler used by the EPG component (see
packages/ui/src/epg.tsx).
Utilities
| Export | Purpose |
|---|---|
dateFormatter, dateInstance, DateFormatter |
Centralised date formatting honouring shell locale + timezone. Use this so dates render consistently across widgets. |
tablet, desktop, xDesktop, xxDesktop, mobileOnly, tabletEnds, desktopEnds, … |
Media-query breakpoint helpers (tabletStartWidth: 768px, xDesktopStartWidth: 1200px, xxDesktopStartWidth: 1600px). For use in styled-components. |
filterTree, getAllKeysFromKey, getAllKeysFromValue, getAllItems, TreeStructure |
Tree-navigation helpers — for TreeSelect data preparation. |
loadBaseStyles() |
Lazy-load antd reset + shaka-player CSS + cron-scheduler CSS. The shell calls this once on boot. |
Player primitives
Player, PlayerRef, VideoEvents, DrmInfo, DrmSetup wrap
shaka-player for the video-player surface used in catalog preview pages.
DRM setup follows the DrmSetup shape — see source for the contract.
Quick reference — common patterns
Render a form field
import { Field, Input, Select } from '@agilecontent/ui';
<Field name="title" label="Title" rules={[{ required: true }]}>
<Input placeholder="Movie title" />
</Field>
<Field name="status" label="Status">
<Select
options={[
{ label: 'Draft', value: 'draft' },
{ label: 'Published', value: 'published' },
]}
/>
</Field>
Render a data table
import { Table, TableTypes } from '@agilecontent/ui';
<Table
dataSource={data.items}
columns={[
{ title: 'ID', dataIndex: 'id', sorter: true },
{ title: 'Source', dataIndex: 'source',
render: TableTypes.RenderType.Source },
{ title: 'Active', dataIndex: 'isActive',
render: TableTypes.RenderType.Boolean },
]}
pagination={false}
/>
Show a confirmation
import { Modal, successMessage } from '@agilecontent/ui';
const onDelete = async () => {
Modal.confirm({
title: 'Delete this record?',
okText: 'Delete', okType: 'danger',
onOk: async () => {
await api.delete(id);
successMessage('Deleted');
},
});
};
Style with theme variables
import styled from 'styled-components';
const Card = styled.div`
background: var(--color-primary-background);
color: var(--color-title);
border: 1px solid var(--color-tertiary-background);
&.is-active {
border-color: var(--color-highlight);
}
`;
Versioning and upgrades
@agilecontent/ui follows semver. The shell pins a specific minor
version; federated remotes should match (or use a compatible range) to
avoid an antd duplicate-version conflict at federation merge time. Check
the shell's apps/cms/package.json for the canonical pinned version
before upgrading a remote.
When the shell upgrades antd (e.g. 5.x → 5.y), the
@agilecontent/ui major version usually bumps. Federated remotes need
to bump in lockstep — there's no way to run two antd majors on the same
page.
When to bypass @agilecontent/ui
Almost never. If you find yourself reaching for import { ... } from 'antd' directly inside a federated widget, stop and check:
- Is the component you want already exported from
@agilecontent/ui? (Most are — see the full list insrc/index.tsx.) - If not, does it warrant adding a wrapper to
@agilecontent/ui? File an issue againstMibFrontEnd. - If your component is one-off and won't be used outside your remote,
it's OK to use raw
antdfor that single case — but declare antd as a peer dependency in your remote'spackage.jsonand add it to the federationsharedarray so the deduplication still works.
The pattern to avoid is silently bundling a second antd into your
remote. That's what breaks Modal.confirm, notification, and the
shell's ConfigProvider.
See also
- Component Authoring — how a custom widget plugs into the shell
- Theming — CSS custom variables that every
@agilecontent/uiprimitive reads - Localization — how the controls pick up the active language
- Built-in Components — the stock widgets,
built entirely from
@agilecontent/ui - Ant Design docs — the upstream library most of these wrap