Global Dialogs Slice Pattern
SkillDev toolsGlobal dialogs Redux slice pattern for MVVM dialog state management
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Global Dialogs Slice Pattern skill
What this skill tells your AI
The instructions your AI receives, as published by ledgerhq/ledger-live in .agents/skills/dialogs-slice/SKILL.md and read by ahel’s review.
Centralized Redux state for global dialog open/close, avoiding one slice per dialog.
When to use
Use this pattern for dialogs that are:
- Mounted at the app root (e.g. in
Default.tsx) - Triggered from distant parts of the component tree (e.g. a settings button opening a dialog rendered at root level)
Do NOT use this for dialogs that are local to a single component or screen -- prefer a simple useState instead.
Architecture
- One shared slice:
renderer/reducers/dialogs.tsmanages all global dialog states via aRecord<DialogId, boolean> - Single source of truth:
DIALOG_IDSruntime array defines all IDs;DialogIdtype is derived from it via(typeof DIALOG_IDS)[number] - Feature wrappers: Each feature creates a thin
*Dialog.tsfile that re-exports typed helpers bound to its ID - Single mount point:
mvvm/features/GlobalDialogs/index.tsxrenders all global dialog components at the app root
Adding a new global dialog
Step 1: Register the dialog ID
Add the new ID to the DIALOG_IDS array in renderer/reducers/dialogs.ts. The DialogId type updates automatically:
export const DIALOG_IDS = ["RELEASE_NOTES", "MY_NEW_DIALOG"] as const;
// DialogId is now "RELEASE_NOTES" | "MY_NEW_DIALOG"
Step 2: Create the feature dialog file
Create myFeature/myNewDialog.ts in the feature folder:
import {
openDialog,
closeDialog,
selectIsDialogOpen,
type DialogId,
} from "~/renderer/reducers/dialogs";
import type { State } from "~/renderer/reducers";
const DIALOG_ID: DialogId = "MY_NEW_DIALOG";
export const openMyNewDialog = () => openDialog(DIALOG_ID);
export const closeMyNewDialog = () => closeDialog(DIALOG_ID);
export const selectIsMyNewDialogOpen = (state: State) => selectIsDialogOpen(state, DIALOG_ID);
Step 3: Use in the view model
import { selectIsMyNewDialogOpen, closeMyNewDialog } from "../myNewDialog";
const useMyNewDialogViewModel = () => {
const isOpen = useSelector(selectIsMyNewDialogOpen);
const dispatch = useDispatch();
const onClose = useCallback(() => dispatch(closeMyNewDialog()), [dispatch]);
return { isOpen, onClose };
};
Step 4: Mount in GlobalDialogs
Add the dialog component to mvvm/features/GlobalDialogs/index.tsx:
import MyNewDialog from "LLD/features/MyFeature";
const GlobalDialogs = () => (
<>
<ModularDialogRoot />
<SendFlowRoot />
<ReleaseNotes />
<MyNewDialog />
</>
);
Step 5: Trigger from anywhere
import { openMyNewDialog } from "LLD/features/MyFeature/myNewDialog";
dispatch(openMyNewDialog());
Rules
- NEVER create a dedicated Redux slice just for dialog open/close state
- NEVER use this pattern for dialogs local to a single component -- use
useStateinstead - ALWAYS add new dialog IDs to the
DIALOG_IDSarray (the type is derived automatically) - ALWAYS use SCREAMING_SNAKE_CASE for dialog IDs
- ALWAYS mount new global dialogs in
GlobalDialogs/index.tsx, not directly inDefault.tsx - Feature dialog files should only contain the ID constant and re-exported typed helpers
- No modification to
reducers/index.tsorDefault.tsxis needed when adding new dialogs
Reference
- Slice:
apps/ledger-live-desktop/src/renderer/reducers/dialogs.ts - Mount point:
apps/ledger-live-desktop/src/mvvm/features/GlobalDialogs/index.tsx - Example:
apps/ledger-live-desktop/src/mvvm/features/ReleaseNotes/releaseNotesDialog.ts
Signals
- GitHub stars
- 618
- Forks
- 490
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
dialogs-slice- Source
- github.com/ledgerhq/ledger-live