一键导入
pimcore-studio-ui-layout-components
Fundamental layout components in Pimcore Studio UI - Content, Box, Flex, Space, ConfigLayout with real-world patterns
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Fundamental layout components in Pimcore Studio UI - Content, Box, Flex, Space, ConfigLayout with real-world patterns
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Use when creating a new color theme or token set for Pimcore Studio UI (a DynamicTypeThemeAbstract subclass registered in the ThemeRegistry), including choosing an extends chain, filling AntD color tokens, seeding the boot palette, and picking colors that meet WCAG AAA contrast
Building filters in Pimcore Studio UI - the declarative defineFilter framework for filter panels and sidebars (createFiltersStore, FiltersRenderer, useFilterQuery) and custom field-filter dynamic types for per-column listing filters (DynamicTypeFieldFilterAbstract, DynamicTypeFieldFilterRegistry). Use this whenever building, customizing, or extending any kind of filtering, filter panel, filter sidebar, or search/filter UI, or adding a new filter type to a listing or data view - even when the user only says "add filtering", "let users filter by X", "filter this list", or "build a filter sidebar".
Building listings in Pimcore Studio UI using the ListingBuilder decorator pattern - sorting, paging, filtering, inline editing, and custom decorators
UX and UI design conventions for Pimcore Studio - layout, spacing, action labels, writing style, and design principles for consistent extensions
Pimcore Studio migration and review checklist — 34-point verification checklist, anti-patterns to avoid, and reference base classes for validating Studio code completeness
Pimcore Studio PHP code style rules — strict types, class modifiers, final/readonly patterns, formatting (120 chars), named arguments, imports, PHPDoc, @throws documentation, and constructor promotion
| name | pimcore-studio-ui-layout-components |
| description | Fundamental layout components in Pimcore Studio UI - Content, Box, Flex, Space, ConfigLayout with real-world patterns |
| metadata | {"audience":"pimcore-developers","focus":"ui-components"} |
Core layout components for building UIs in Pimcore Studio:
LIST-DETAIL-TABS-PATTERN.md)Use this when:
LIST-DETAIL-TABS-PATTERN.md for the complete pattern)BEFORE writing any import, read CRITICAL-IMPORT-PATHS.md.
All examples below use bundle imports (@pimcore/studio-ui-bundle/*). For core development (@sdk/*, @Pimcore/*), see the referenced file.
All layout components use a consistent spacing system:
mini - Smallest spacing (4px)extra-small - Very small spacing (8px)small - Small spacing (12px)medium - Medium spacing (16px) - defaultlarge - Large spacing (24px)extra-large - Extra large spacing (32px)You can also use numbers for pixel-perfect spacing.
The Content component is the primary container for page content sections. Use it as a wrapper for main content areas.
import { Content } from '@pimcore/studio-ui-bundle/components'
const MyPage = () => {
return (
<Content padded>
<h1>My Page Title</h1>
<p>Content goes here</p>
</Content>
)
}
import { Content } from '@pimcore/studio-ui-bundle/components'
import { useGetDataQuery } from '@pimcore/studio-ui-bundle/api/data'
const DataView = () => {
const { data, isLoading } = useGetDataQuery()
return (
<Content loading={isLoading} padded>
{data && <DataDisplay data={data} />}
</Content>
)
}
Why use loading on Content:
// Fine-grained padding control
<Content
padded
padding={{
x: 'extra-small', // Horizontal padding
y: 'small' // Vertical padding
}}
>
{/* Content */}
</Content>
// Automatic spacing between direct children
<Content
padded
gap="small"
>
<SearchForm />
<ResultsTable />
<Pagination />
</Content>
// No content, no padding
<Content none />
interface ContentProps {
padded?: boolean // Apply default padding
padding?: { // Custom padding control
x?: SpacingSize // Horizontal
y?: SpacingSize // Vertical
top?: SpacingSize
bottom?: SpacingSize
left?: SpacingSize
right?: SpacingSize
}
loading?: boolean // Show loading skeleton
gap?: SpacingSize // Gap between children
none?: boolean // Empty state
children?: ReactNode
}
import { Content, Header } from '@pimcore/studio-ui-bundle/components'
export const ExampleWidget = () => {
const { data, isLoading } = useWidgetDataQuery()
return (
<Content
loading={isLoading}
padded
padding={{
x: 'extra-small',
y: 'extra-small'
}}
gap="small"
>
<Header title="Example Widget" />
<div>Select a widget:</div>
{data && <WidgetList items={data} />}
</Content>
)
}
The Box component is a utility wrapper for adding spacing (margin/padding) around elements. Use it for fine-grained spacing control.
import { Box } from '@pimcore/studio-ui-bundle/components'
const MyComponent = () => {
return (
<Box padding="small">
<SomeContent />
</Box>
)
}
// String value (all sides)
<Box padding="small">
<Panel />
</Box>
// Object for specific sides
<Box padding={{ x: 'extra-small', y: 'small' }}>
<Panel />
</Box>
// Individual sides
<Box padding={{ top: 'small', bottom: 'large', left: 'extra-small', right: 'extra-small' }}>
<Panel />
</Box>
// Add margin to separate sections
<Box margin={{ top: 'small', bottom: 'small' }}>
<Section />
</Box>
// Specific side margins
<Box margin={{ bottom: 'large' }}>
<Section />
</Box>
<Box
padding="extra-small"
margin={{ top: 'extra-small', bottom: 'extra-small' }}
>
<Flex align="center" gap="extra-small">
<Icon value="info" />
<Text>Important information</Text>
</Flex>
</Box>
interface BoxProps {
padding?: SpacingSize | {
x?: SpacingSize
y?: SpacingSize
top?: SpacingSize
bottom?: SpacingSize
left?: SpacingSize
right?: SpacingSize
}
margin?: SpacingSize | {
x?: SpacingSize
y?: SpacingSize
top?: SpacingSize
bottom?: SpacingSize
left?: SpacingSize
right?: SpacingSize
}
children?: ReactNode
}
import { Box, Flex, Space } from '@pimcore/studio-ui-bundle/components'
export const ScheduleToolbar = () => {
return (
<Box padding={{ y: 'small' }}>
<Space>
<Text strong>Archived Schedules</Text>
<IconTextButton
icon={{ value: 'trash' }}
onClick={handleDelete}
>
Delete All
</IconTextButton>
</Space>
</Box>
)
}
Use Content when:
Use Box when:
The Flex component provides flexible layout control with CSS Flexbox. Use it for aligning, distributing, and organizing elements.
import { Flex } from '@pimcore/studio-ui-bundle/components'
const Toolbar = () => {
return (
<Flex align="center" gap="small">
<Icon value="search" />
<Input placeholder="Search..." />
<Button>Search</Button>
</Flex>
)
}
// Stack elements vertically
<Flex vertical gap="small">
<Title>Settings</Title>
<SettingsPanel />
<SaveButton />
</Flex>
// Center items horizontally and vertically
<Flex align="center" justify="center">
<LoadingSpinner />
</Flex>
// Align items to start/end
<Flex align="start" justify="space-between">
<Title>Header</Title>
<IconButton icon={{ value: 'close' }} />
</Flex>
// Use spacing system
<Flex gap="large">
<Panel />
<Panel />
<Panel />
</Flex>
// Or specific pixel value
<Flex gap={24}>
<Item />
<Item />
</Flex>
// Complex toolbar with multiple sections
<Flex justify="space-between" align="center">
<Flex gap="small">
<IconButton icon={{ value: 'arrow-left' }} />
<Title>Document Editor</Title>
</Flex>
<Flex gap="mini">
<Button>Cancel</Button>
<Button type="primary">Save</Button>
</Flex>
</Flex>
// Stretch to fill parent height
<Flex
className="absolute-stretch"
vertical
justify="space-between"
>
<Content padded>
{/* Main content */}
</Content>
<Toolbar justify="flex-end">
<Button>Save</Button>
</Toolbar>
</Flex>
interface FlexProps {
vertical?: boolean // Stack vertically
align?: 'start' | 'center' | 'end' | 'stretch' | 'baseline'
justify?: 'start' | 'center' | 'end' | 'space-between' | 'space-around' | 'space-evenly'
gap?: SpacingSize | number // Gap between children
wrap?: boolean // Allow wrapping
className?: string
style?: CSSProperties
children?: ReactNode
}
import { Flex, Icon } from '@pimcore/studio-ui-bundle/components'
export const EmailCard = ({ email }) => {
return (
<Flex align="center" gap="extra-small">
<Icon value="send-03" />
<span>{email.subject}</span>
</Flex>
)
}
import { Flex, Content, Toolbar, Button } from '@pimcore/studio-ui-bundle/components'
export const AppearanceForm = () => {
return (
<Flex
className="appearance-branding-form absolute-stretch"
justify="space-between"
vertical
>
<Content padded>
<FormKit formProps={{ form }}>
{/* Form fields */}
</FormKit>
</Content>
<Toolbar justify="flex-end">
<Button onClick={handleCancel}>Cancel</Button>
<Button type="primary" onClick={handleSave}>Save</Button>
</Toolbar>
</Flex>
)
}
The Space component adds consistent spacing between children. It's simpler than Flex when you just need spacing without alignment control.
import { Space } from '@pimcore/studio-ui-bundle/components'
const ButtonGroup = () => {
return (
<Space size="small">
<Button>Cancel</Button>
<Button type="primary">Save</Button>
</Space>
)
}
// Stack with spacing
<Space direction="vertical" size="large">
<ColorPanel />
<ImagePanel />
<LogoPanel />
</Space>
import { Space, TooltipIcon } from '@pimcore/studio-ui-bundle/components'
const PanelHeader = ({ title, tooltip }) => {
return (
<Space size="extra-small">
{title}
<TooltipIcon tooltip={tooltip} />
</Space>
)
}
// Switch with label
<Space
className="pimcore-schedule-toolbar__filters__active-switch"
size="extra-small"
>
<Switch
labelLeft="Show active only"
onChange={setActiveOnly}
value={activeOnly}
/>
</Space>
// Fill available width
<Space
className="w-full"
direction="vertical"
size="extra-small"
>
<FormItem1 />
<FormItem2 />
<FormItem3 />
</Space>
interface SpaceProps {
size?: SpacingSize // Spacing size
direction?: 'horizontal' | 'vertical' // Layout direction (default: horizontal)
className?: string
children?: ReactNode
}
import { Space, IconButton } from '@pimcore/studio-ui-bundle/components'
export const ColumnsConfiguration = () => {
return (
<Space size="mini">
<IconButton
icon={{ value: 'trash' }}
onClick={handleRemove}
theme="secondary"
/>
<IconButton
icon={{ value: 'drag-handle' }}
theme="secondary"
/>
</Space>
)
}
Use Space when:
Use Flex when:
The ConfigLayout component creates a two-column layout with an optional resizable divider. Perfect for sidebar + content layouts.
ConfigLayout has a standard pattern for managing collections of entities (settings, configurations, CRUD interfaces).
👉 See LIST-DETAIL-TABS-PATTERN.md for the complete implementation guide!
This pattern provides:
* in tab labelsPerfect for: Settings pages, entity management, configuration UIs, list-based CRUD
Examples in codebase: Target Groups (personalization-bundle), Email Log, Reports Editor, Field Definitions
import { ConfigLayout } from '@pimcore/studio-ui-bundle/components'
const EditorLayout = () => {
return (
<ConfigLayout
leftItem={{
children: <Sidebar />
}}
rightItem={{
children: <MainContent />
}}
/>
)
}
// Allow user to resize the sidebar
// Only specify size, minSize, maxSize when you need resizeAble
<ConfigLayout
leftItem={{
minSize: 250,
maxSize: 350,
children: <DetailSidebar />
}}
resizeAble
rightItem={{
children: <DetailContent />
}}
/>
interface ConfigLayoutProps {
leftItem: {
children: ReactNode
minSize?: number // Minimum width in pixels (only when resizeAble)
maxSize?: number // Maximum width in pixels (only when resizeAble)
size?: number // Default width in pixels (rarely needed, use default)
}
rightItem: {
children: ReactNode
}
resizeAble?: boolean // Enable resizing
}
Note: Usually you don't need to specify size, minSize, or maxSize. The default width is fine for most cases. Only use these when you specifically need a resizable sidebar.
import { ConfigLayout, Content } from '@pimcore/studio-ui-bundle/components'
export const ReportsEditor = () => {
return (
<Content loading={isLoading}>
<ConfigLayout
leftItem={{
children: (
<ReportsSidebar
handleOpenReport={handleOpenReport}
isFetching={isFetching}
isLoading={isLoading}
/>
)
}}
rightItem={{
children: <ReportContent />
}}
/>
</Content>
)
}
import { ConfigLayout, ContentLayout, Toolbar, Flex, IconButton } from '@pimcore/studio-ui-bundle/components'
export const FieldDefinitionDetail = () => {
return (
<ContentLayout
className="absolute-stretch"
renderToolbar={
<Toolbar>
<Flex gap="mini">
<IconButton icon={{ value: 'refresh' }} onClick={handleRefresh} />
</Flex>
<DetailSave />
</Toolbar>
}
>
<Content loading={isLoading}>
<ConfigLayout
leftItem={{
minSize: 250,
maxSize: 350,
children: <DetailSidebar />
}}
resizeAble
rightItem={{
children: (
<Content padded>
<FormKit formProps={{ form }}>
{/* Field configuration form */}
</FormKit>
</Content>
)
}}
/>
</Content>
</ContentLayout>
)
}
Use case: Edit forms with save/cancel actions
import { Flex, Content, Toolbar, Button } from '@pimcore/studio-ui-bundle/components'
export const EntityEditForm = ({ entity, onSave, onCancel }) => {
return (
<Flex
className="absolute-stretch"
vertical
justify="space-between"
>
<Content padded>
<FormKit formProps={{ form, onFinish: onSave }}>
{/* Form fields */}
</FormKit>
</Content>
<Toolbar justify="flex-end">
<Button onClick={onCancel}>Cancel</Button>
<Button type="primary" htmlType="submit">Save</Button>
</Toolbar>
</Flex>
)
}
Use case: Settings pages, editors with navigation
import { ConfigLayout, Content } from '@pimcore/studio-ui-bundle/components'
export const SettingsPage = () => {
return (
<ConfigLayout
leftItem={{
children: <SettingsNavigation />
}}
rightItem={{
children: (
<Content padded>
<SettingsContent />
</Content>
)
}}
/>
)
}
Use case: Multi-section forms, complex toolbars, data views
import { Content, Space, Box, Flex } from '@pimcore/studio-ui-bundle/components'
// Multi-section form
export const MultiSectionForm = () => {
return (
<Content padded padding={{ x: 'extra-small', y: 'extra-small' }}>
<Space direction="vertical" size="large">
<Box><Title level={2}>Basic Info</Title><BasicInfoForm /></Box>
<Box><Title level={2}>Advanced</Title><AdvancedForm /></Box>
</Space>
</Content>
)
}
// Complex toolbar with grouped actions
export const ComplexToolbar = () => {
return (
<Flex justify="space-between" align="center">
<Flex gap="small"><IconButton icon={{ value: 'arrow-left' }} /><Title level={3}>Editor</Title></Flex>
<Space size="mini"><IconButton icon={{ value: 'undo' }} /><IconButton icon={{ value: 'redo' }} /></Space>
<Flex gap="mini"><Button>Cancel</Button><Button type="primary">Save</Button></Flex>
</Flex>
)
}
// Data view with loading/empty states
export const DataView = () => {
const { data, isLoading } = useGetDataQuery()
return (
<Content loading={isLoading} padded>
{data?.length ? (
<Space direction="vertical" size="small">
{data.map(item => <DataCard key={item.id} item={item} />)}
</Space>
) : (
<EmptyState message="No data" />
)}
</Content>
)
}
✅ Do this:
<Content loading={isLoading} padded>
<MyForm />
</Content>
❌ Not this:
{isLoading ? <Spinner /> : (
<div style={{ padding: '16px' }}>
<MyForm />
</div>
)}
Why: Content provides consistent loading UI and spacing.
✅ Do this:
<Box padding="small" margin={{ bottom: 'large' }}>
<Section />
</Box>
❌ Not this:
<div style={{ padding: '12px', marginBottom: '24px' }}>
<Section />
</div>
Why: Uses design system values, consistent across app.
✅ Do this:
<Space size="small">
<Button>Cancel</Button>
<Button type="primary">Save</Button>
</Space>
❌ Not this:
<Flex gap="small">
<Button>Cancel</Button>
<Button type="primary">Save</Button>
</Flex>
Why: Space is simpler when you don't need alignment.
✅ Do this:
<Flex align="center" justify="space-between">
<Title>Header</Title>
<IconButton icon={{ value: 'close' }} />
</Flex>
✅ Also good:
<Space size="small">
<Button>Action 1</Button>
<Button>Action 2</Button>
</Space>
Why: Choose the right tool - Flex when you need alignment, Space when you don't.
✅ Do this:
// Basic usage - no size specs needed
<ConfigLayout
leftItem={{
children: <Sidebar />
}}
rightItem={{
children: <Content padded><MainView /></Content>
}}
/>
// Only add size props when you need resizeAble
<ConfigLayout
leftItem={{
minSize: 250,
maxSize: 400,
children: <Sidebar />
}}
resizeAble
rightItem={{
children: <Content padded><MainView /></Content>
}}
/>
Why: Built-in two-column layout with optional resize behavior, consistent UX. Use default width unless you specifically need custom sizing.
✅ Do this:
<Flex vertical className="absolute-stretch">
<Content padded>
<Space direction="vertical" size="large">
<Section1 />
<Section2 />
</Space>
</Content>
<Toolbar justify="flex-end">
<Button>Save</Button>
</Toolbar>
</Flex>
Why: Composable components create flexible, maintainable layouts.
✅ Do this:
<Content loading={isLoading} padded>
{data && <DataDisplay data={data} />}
</Content>
❌ Not this:
<Content padded>
{isLoading ? <Skeleton /> : data && <DataDisplay data={data} />}
</Content>
Why: Content has built-in loading skeleton that prevents layout shift.
// BAD - nested Content components
<Content padded>
<Content padded>
<Form />
</Content>
</Content>
✅ Use Box or Flex for inner sections:
// GOOD
<Content padded>
<Box padding="small">
<Form />
</Box>
</Content>
// BAD
<div style={{ margin: '8px 16px' }}>
<Component />
</div>
✅ Use Box or spacing props:
// GOOD
<Box margin={{ y: 'extra-small', x: 'small' }}>
<Component />
</Box>
// BAD - mixing px values with spacing system
<Flex gap={8}>
<Box padding="small">
<Component />
</Box>
</Flex>
✅ Be consistent:
// GOOD
<Flex gap="extra-small">
<Box padding="small">
<Component />
</Box>
</Flex>
// BAD - unnecessary size specification without resizeAble
<ConfigLayout
leftItem={{
size: 300,
minSize: 250,
maxSize: 400,
children: <Sidebar />
}}
rightItem={{ children: <Main /> }}
/>
✅ Use default size, only specify when resizeAble:
// GOOD - use default width (most cases)
<ConfigLayout
leftItem={{ children: <Sidebar /> }}
rightItem={{ children: <Main /> }}
/>
// GOOD - only specify sizes when you need resizeAble
<ConfigLayout
leftItem={{
minSize: 250,
maxSize: 400,
children: <Sidebar />
}}
resizeAble
rightItem={{ children: <Main /> }}
/>
// BAD - overkill for simple spacing
<Flex direction="horizontal" gap="small" align="start">
<Button>Cancel</Button>
<Button>Save</Button>
</Flex>
✅ Use Space for simple cases:
// GOOD
<Space size="small">
<Button>Cancel</Button>
<Button>Save</Button>
</Space>
| Need | Use | Props |
|---|---|---|
| Main content wrapper | Content | padded, loading, gap |
| Add spacing around element | Box | padding, margin |
| Align/distribute elements | Flex | align, justify, gap, vertical |
| Simple spacing | Space | size, direction |
| Sidebar + content | ConfigLayout | leftItem, rightItem, resizeAble |
mini = 4pxextra-small = 8pxsmall = 12pxmedium = 16px (default)large = 24pxextra-large = 32pxAfter mastering layout components: