ワンクリックで
pimcore-studio-ui-notifications-toasts
Displaying notification messages and toasts in Pimcore Studio UI using the useMessage hook
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Displaying notification messages and toasts in Pimcore Studio UI using the useMessage hook
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-notifications-toasts |
| description | Displaying notification messages and toasts in Pimcore Studio UI using the useMessage hook |
| metadata | {"audience":"pimcore-developers","focus":"ui-components-notifications"} |
How to display notification messages (toasts) in Pimcore Studio UI:
Use this when:
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.
The useMessage hook provides a message API for displaying toast notifications.
import { useMessage } from '@pimcore/studio-ui-bundle/components'
import { useTranslation } from 'react-i18next'
export const MyComponent = (): React.JSX.Element => {
const messageApi = useMessage()
const { t } = useTranslation()
const handleSave = async (): Promise<void> => {
try {
await saveData()
await messageApi.success(t('save-success'))
} catch (error) {
await messageApi.error({
content: error.message
})
}
}
return (
<Button onClick={ handleSave }>
{t('save')}
</Button>
)
}
interface MessageInstance {
success: (content: string | ArgsProps, duration?, onClose?) => MessageType
error: (content: string | ArgsProps, duration?, onClose?) => MessageType
warning: (content: string | ArgsProps, duration?, onClose?) => MessageType
info: (content: string | ArgsProps, duration?, onClose?) => MessageType
loading: (content: string | ArgsProps, duration?, onClose?) => MessageType
open: (config: ArgsProps) => MessageType
}
Show success messages after successful operations.
// Simple text message
await messageApi.success(t('save-success'))
// With custom duration (seconds)
await messageApi.success(t('save-success'), 5)
// With callback after close
await messageApi.success(t('save-success'), 3, () => {
console.log('Message closed')
})
messageApi.success({
content: t('operation-completed'),
duration: 3, // seconds (0 = never auto-close)
onClose: () => {
// Callback when message closes
}
})
// File: asset/editor/toolbar/save-button/save-button.tsx
import { useMessage } from '@pimcore/studio-ui-bundle/components'
import { useTranslation } from 'react-i18next'
export const EditorToolbarSaveButton = (): React.JSX.Element => {
const { t } = useTranslation()
const messageApi = useMessage()
const [saveAsset, { isSuccess }] = useAssetUpdateByIdMutation()
useEffect(() => {
const handleSuccessEvent = async (): Promise<void> => {
if (isSuccess) {
await messageApi.success(t('save-success'))
}
}
handleSuccessEvent().catch((error) => {
console.error(error)
})
}, [isSuccess])
return (
<Button
onClick={ handleSave }
type="primary"
>
{t('save')}
</Button>
)
}
Display error messages when operations fail.
// Simple text error
await messageApi.error(t('save-failed'))
// With error details
await messageApi.error({
content: t('save-failed')
})
try {
await saveData()
} catch (error) {
await messageApi.error({
content: error.message || t('unknown-error')
})
}
// File: auth/components/login-form/login-form.tsx
import { useMessage } from '@pimcore/studio-ui-bundle/components'
export const LoginForm = (): React.JSX.Element => {
const messageApi = useMessage()
const handleAuthentication = async (event: React.FormEvent): Promise<void> => {
try {
event.preventDefault()
const response = await login({ credentials: formState })
if (response.error !== undefined) {
await messageApi.error({
content: t('login-failed')
})
} else {
dispatch(setAuthState(true))
}
} catch (e: any) {
await messageApi.error({
content: e.message
})
}
}
return (
<form onSubmit={ handleAuthentication }>
{/* form fields */}
</form>
)
}
// File: data-object/editor/providers/edit-form-provider.tsx
useEffect(() => {
if (isError) {
messageApi.error(t('auto-save-failed'))
}
}, [isError])
Display warning messages for non-critical issues.
messageApi.warning(t('unsaved-changes'))
messageApi.warning({
content: t('operation-may-take-time'),
duration: 5
})
const handleDelete = (): void => {
if (hasRelatedItems) {
messageApi.warning({
content: t('item-has-dependencies'),
duration: 0 // Don't auto-close
})
return
}
performDelete()
}
Display informational messages.
messageApi.info(t('loading-data'))
messageApi.info({
content: t('background-task-started'),
duration: 3
})
The useMessage hook automatically adds custom icons for info messages:
// Automatically includes info-circle icon
messageApi.info(t('processing'))
Display loading messages for ongoing operations.
const hideLoading = messageApi.loading(t('saving'))
// Later, hide the message manually
hideLoading()
// Auto-hide after 3 seconds
messageApi.loading(t('processing'), 3)
const handleExport = async (): Promise<void> => {
const hide = messageApi.loading(t('exporting-data'), 0)
try {
await performExport()
hide()
await messageApi.success(t('export-complete'))
} catch (error) {
hide()
await messageApi.error(t('export-failed'))
}
}
interface ArgsProps {
content: React.ReactNode // Message content
duration?: number // Duration in seconds (0 = manual close)
onClose?: VoidFunction // Callback when closed
icon?: React.ReactNode // Custom icon element
key?: string | number // Unique key for the message
className?: string // Custom CSS class
style?: React.CSSProperties // Custom inline styles
onClick?: (e: React.MouseEvent) => void // Click handler
}
// Show for 5 seconds (default is 3)
messageApi.success(t('saved'), 5)
// Never auto-close (must be closed manually)
messageApi.success({
content: t('important-message'),
duration: 0
})
// Close immediately
messageApi.success(t('quick-message'), 0.5)
// Store reference to close manually
const hide = messageApi.success({
content: t('uploading'),
duration: 0
})
// Later, close the message
setTimeout(() => {
hide()
}, 5000)
const handleMultiStepOperation = async (): Promise<void> => {
// Step 1
const loading = messageApi.loading(t('step-1'))
await performStep1()
loading()
// Step 2
const loading2 = messageApi.loading(t('step-2'))
await performStep2()
loading2()
// Final success
await messageApi.success(t('all-steps-complete'))
}
const [saveData, { isLoading, isSuccess, isError, error }] = useSaveMutation()
const messageApi = useMessage()
const { t } = useTranslation()
useEffect(() => {
if (isSuccess) {
void messageApi.success(t('save-success'))
}
}, [isSuccess])
useEffect(() => {
if (isError) {
void messageApi.error(t('save-failed'))
}
}, [isError])
// File: element/actions/copy-paste/tree-copy-paste-context.tsx
const handleCopy = (element: Element): void => {
setCopiedElement({ element, mode: 'copy' })
void messageApi.success(t('element.tree.copy-success-description', {
type: element.type,
path: element.path
}))
}
const handleCut = (element: Element): void => {
setCopiedElement({ element, mode: 'cut' })
void messageApi.success(t('element.tree.cut-success-description', {
type: element.type,
path: element.path
}))
}
// File: dynamic-types/.../hotspot-image/footer.tsx
const handleClearData = async (): Promise<void> => {
clearHotspots()
await messageApi.success(t('hotspots.data-cleared'))
}
const handleBatchDelete = async (items: Item[]): Promise<void> => {
const loading = messageApi.loading(t('deleting-items', { count: items.length }), 0)
try {
const results = await Promise.allSettled(
items.map(item => deleteItem(item.id))
)
loading()
const succeeded = results.filter(r => r.status === 'fulfilled').length
const failed = results.filter(r => r.status === 'rejected').length
if (failed === 0) {
await messageApi.success(t('batch-delete-success', { count: succeeded }))
} else {
await messageApi.warning(t('batch-delete-partial', {
succeeded,
failed
}))
}
} catch (error) {
loading()
await messageApi.error(t('batch-delete-failed'))
}
}
// File: tags/hooks/use-tag-config.tsx
const handleCreateTag = async (): Promise<void> => {
const result = await createTag(tagData)
if (result.success) {
messageApi.success({
content: t('tag.created', { name: tagData.name }),
duration: 3
})
}
}
const handleUpdateTag = async (): Promise<void> => {
const result = await updateTag(tagData)
if (result.success) {
messageApi.success({
content: t('tag.updated'),
duration: 2
})
}
}
const handleDeleteTag = async (): Promise<void> => {
const result = await deleteTag(tagId)
if (result.success) {
messageApi.success({
content: t('tag.deleted'),
duration: 2
})
}
}
| Type | Use Case | Default Duration | Auto Icon |
|---|---|---|---|
success | Successful operations | 3s | ✓ (checkmark) |
error | Failed operations | 3s | ✓ (error) |
warning | Non-critical issues | 3s | ✓ (warning) |
info | Information | 3s | ✓ (info-circle, custom) |
loading | Ongoing operations | Manual | ✓ (spinner) |
// DON'T - Hardcoded text
messageApi.success('Saved successfully')
// DO - Use i18n
messageApi.success(t('save-success'))
// DON'T - No error feedback
const handleSave = async (): Promise<void> => {
await saveData()
messageApi.success(t('saved'))
}
// DO - Handle errors
const handleSave = async (): Promise<void> => {
try {
await saveData()
await messageApi.success(t('saved'))
} catch (error) {
await messageApi.error(t('save-failed'))
}
}
// DON'T - Not awaiting (may cause timing issues)
messageApi.success(t('saved'))
doNextThing()
// DO - Await message
await messageApi.success(t('saved'))
doNextThing()
// DON'T - User can't see console
console.log('Data saved')
// DO - Show user-visible message
messageApi.success(t('save-success'))
// DON'T - Spam the user
items.forEach(item => {
messageApi.success(t('item-saved', { name: item.name }))
})
// DO - Single summary message
messageApi.success(t('items-saved', { count: items.length }))
// DON'T - Using error for non-errors
if (items.length === 0) {
messageApi.error(t('no-items')) // Not an error!
}
// DO - Use info or warning
if (items.length === 0) {
messageApi.info(t('no-items'))
}
// For any user action, provide feedback
const handleAction = async (): Promise<void> => {
try {
await performAction()
await messageApi.success(t('action-complete')) // Always confirm
} catch (error) {
await messageApi.error(t('action-failed')) // Always report errors
}
}
// Success: Completed operations
messageApi.success(t('saved'))
// Error: Failed operations
messageApi.error(t('save-failed'))
// Warning: Potential issues
messageApi.warning(t('unsaved-changes'))
// Info: Informational updates
messageApi.info(t('data-loading'))
// Loading: Ongoing operations
messageApi.loading(t('processing'))
// DO - Short and clear
messageApi.success(t('saved'))
// AVOID - Too verbose
messageApi.success(t('your-changes-have-been-successfully-saved-to-the-database'))
// Include dynamic data in messages
messageApi.success(t('item-created', { name: item.name }))
messageApi.warning(t('items-remaining', { count: remaining }))
// Show loading, then result
const handleExport = async (): Promise<void> => {
const hide = messageApi.loading(t('exporting'), 0)
try {
const result = await exportData()
hide()
await messageApi.success(t('export-complete'))
} catch (error) {
hide()
await messageApi.error(t('export-failed'))
}
}
// Simple success
await messageApi.success(t('saved'))
// Simple error
await messageApi.error(t('failed'))
// With duration
messageApi.success(t('saved'), 5)
// Never auto-close
messageApi.warning({
content: t('warning'),
duration: 0
})
// Loading (manual close)
const hide = messageApi.loading(t('loading'), 0)
hide() // Close it later
// With interpolation
messageApi.success(t('created', { name: item.name }))