| name | toxen-design-patterns |
| description | Toxen UI/UX design system, theming, and consistency conventions. Use when building or modifying any Toxen frontend: React components, panels, buttons, forms, modals, notifications, icons, styling/SCSS, theme colors, or when reviewing UI code for consistency. Ensures new work matches Toxen theming (CSS variables), user-friendliness, and established component patterns. Trigger phrases: "add a button/panel/modal", "style this", "make it themeable", "match Toxen style", "is this consistent", "notify the user", "add an icon", "new settings option". |
Toxen Design Patterns & Consistency
Toxen is an Electron 30 + React 18 + TypeScript music player targeting desktop and web through the toxenapi abstraction. This skill captures the design system so new work stays themeable, consistent, and user-friendly.
When to Use
- Building or editing any React component, panel, form, button, modal, or notification.
- Adding or changing styles (SCSS), colors, spacing, or icons.
- Adding a settings option or per-song/playlist override.
- Reviewing frontend code for theming/consistency compliance.
Golden Rules (never violate)
- Never hardcode colors. Always use theme CSS variables (
var(--accent-color), etc.). A user's theme must be able to recolor everything.
- Never hardcode path separators. Use
toxenapi.joinPath(), toxenapi.getBasename(), never / or \.
- Never assume desktop. Guard filesystem/Electron code with
toxenapi.isDesktop() and provide a web fallback or toxenapi.throwDesktopOnly("operation").
- Never use Font Awesome. Icons come from
@tabler/icons-react (see Icons).
- Never
alert()/console-only user feedback. Use Toxen.log/warn/error/notify (see User Feedback).
- Respect the settings hierarchy: playlist-specific → per-song → global. Don't read global settings when a song/playlist override may apply.
Theming (CSS Variables)
All theming flows through CSS custom properties generated by Theme.parseToCSS() and injected via ThemeContainer. When styling, reference these variables — do not invent hex values.
Core colors: --primary-bg, --secondary-bg, --tertiary-bg, --accent-color, --accent-color-rgb
Accent derivations (calculated): --accent-color-dim, --accent-color-hover, --accent-color-active
Text: --text-primary, --text-secondary, --text-muted, --text-inverse, --text-primary-rgb
Surfaces: --surface-bg, --surface-bg-hover, --surface-bg-active
Borders: --border-primary, --border-secondary, --border-focus
Status: --success-color, --warning-color, --error-color
Motion: --transition-normal
Guidelines:
- Use
--accent-color for interactive/brand elements; use its -dim/-hover/-active variants for backgrounds and states, not new opacities.
- Use
rgba(var(--accent-color-rgb), <alpha>) when you need transparency.
- Use status colors semantically: success = confirm/save, warning = caution, error = destructive/failure.
- To add a new themeable value, add a
ThemeStyleItem to ThemeStyleTemplate in src/app/toxen/Theme.ts (with cssVariable, type, defaultValue, parser) so it appears in the Theme Editor — don't add loose variables.
Styling (SCSS)
- Global utility/base styles live in src/app/tx.scss; app layout in src/app/ToxenApp.scss. Component styles live next to the component (
Component.scss, imported by the .tsx).
- Reuse the
tx- utility classes rather than re-styling from scratch:
.tx-btn (+ variants .tx-btn-action, .tx-btn-warning, .tx-btn-cancel, .tx-btn-next)
.tx-form-field for inputs/selects
.tx-whitespace-nowrap
- Match the existing visual language:
border-radius: 10px, gradient backgrounds from accent variants, backdrop-filter: blur(...), cubic-bezier(0.25, 0.8, 0.25, 1) transitions, subtle translateY/scale on hover, and a focus ring using --accent-color-dim.
- For web-only or desktop-only styling, use the custom
ToxenIsWeb() SCSS function (configured in vite.web.config.ts) instead of runtime branching.
UI Components
Priority order when picking a component:
- Mantine UI (
@mantine/core v7) — default for Buttons, Stack, Group, Tabs, Select, TextInput, NumberInput, Modal, etc. Icons go in leftSection (Buttons/Tabs) or the relevant icon/section prop.
- Toxen custom primitives — e.g. the custom Button (
<Button txStyle="action" txDisabled={...} disabledTitle="..." />), Form, FormInput*, Expandable, SelectAsync, Tooltip. Use these when their behavior is needed.
- React Bootstrap — legacy only; do not add new usages, prefer migrating to Mantine.
Component conventions:
- Existing code is largely class components; when editing a class component, stay class-based for consistency. New standalone components should be functional.
- Panels register themselves on the global
Toxen static (e.g. Toxen.songPanel = this) and expose an update() method; global refreshes go through Toxen.updateSongPanels().
- For functional components that must react to shared state, use the controller pattern: create a
Controller subclass, instantiate with useMemo, and subscribe with useController(controller) from src/app/lib/useController.ts. The component re-renders when the controller calls notify().
Icons
- Import named icons from
@tabler/icons-react: import { IconPlayerPlay } from "@tabler/icons-react";
- Use
size="1em" so the icon inherits the surrounding font-size and currentColor (this keeps icons themeable and correctly scaled). Use an explicit numeric size only when a fixed size is required.
- Place icons in Mantine
leftSection/icon props, not as bare siblings, when the host component provides a slot.
User Feedback (Notifications)
Use the global Toxen helpers — never alert() or console-only messaging. Signatures live in src/app/ToxenApp.tsx.
Toxen.log("Saved.", 3000);
Toxen.warn("Heads up…", 4000);
Toxen.error("Something failed: " + e);
const id = Toxen.notify({
title: "Uploading",
content: <>Custom <b>JSX</b> allowed</>,
type: "normal",
expiresIn: 0,
disableClose: true,
});
Guidelines:
expiresIn in ms; use 0/omit for long-running operations, then update/replace the notification when done.
- Keep messages short, action-oriented, and human. Include the failing item name in errors (
Failed to upload ${file.name}).
- Reuse preset errors via
Toxen.sendError("CURRENTLY_EDITING_SONG") where they exist rather than retyping strings.
Modals
Use Mantine modals via the useModals() hook (@mantine/modals):
modals.openModal({ title, children }) for custom content; capture the returned id to close it (modals.closeModal(id)).
modals.openConfirmModal({ title, children, labels, onConfirm }) for confirm/cancel flows.
- Always confirm destructive actions (delete, clear, overwrite) with
openConfirmModal.
Platform Abstraction (desktop vs web)
if (toxenapi.isDesktop()) {
const files = await toxenapi.fs.promises.readdir(dir);
} else {
}
if (!toxenapi.isDesktop()) return toxenapi.throwDesktopOnly("exportSongPackage");
- Access fs/path/Electron/remote only through
toxenapi. On the web controller these members don't exist, so unguarded access is a type + runtime error.
- Gate UI affordances too: if a feature is desktop-only, disable/hide the control on web and explain via a notification when invoked.
Settings & Overrides
- Read/write globals with
Settings.get(key) / Settings.set(key, value); apply with side effects via Settings.apply(partial, save?).
- Respect precedence: a
Song's per-song value (e.g. visualizerColor) overrides global; song.playlistSettings[playlistId] overrides per-song when playing from that playlist.
- New settings go in the
ISettings interface and are surfaced in the Settings panel with a matching Mantine control and a <sup> help description, consistent with neighbors.
Consistency Checklist (run before finishing UI work)
Key References