Frontend Conventions
When to use this skill
Use this skill whenever generating, refactoring, or reviewing React components, custom hooks, or Next.js pages within the main package of the Swiftpost monorepo.
Directory Strictness & Imports
Do not guess import paths. Adhere strictly to the monorepo structure:
- •Pages & Routing: Must reside in
packages/main/src/app(App Router). - •Reusable Components: Reside in
packages/main/src/components. - •Routing/Links: Strictly use
import Link from '@/components/Link';. Do NOT usenext/link. - •UI Components: Must be imported from the Elysium thin wrapper:
import {ComponentName} from '@swiftpost/elysium/ui/base/{ComponentName}';. - •MUI Icons: Standard
@mui/icons-materialimports are permitted. - •Theme: Use
packages/main/src/styles/staticTheme.ts(import { staticTheme } from '@/styles/staticTheme';) to read theme values without forcing"use client". - •Shared Logic: Features and storage logic must go in
packages/main/src/features.
TypeScript & Component Rules
Technical debt will not be tolerated. Enforce the following:
- •No
any:anyis strictly banned. Use proper typing, generics, orunknowncombined with type guards. - •Type Inference: Rely on TS inference where explicit typing is redundant.
- •Component Structure:
- •Always define an
interface Props(only omit if empty, or if truly stateless). - •Use
React.FC<Props>for component definition. - •Always export the props type as
{ComponentName}Propsat the bottom of the file.
- •Always define an
Example: Standard Component
import Box from '@swiftpost/elysium/ui/base/Box';
import Text from '@swiftpost/elysium/ui/base/Text';
interface Props {
foo: string;
children: React.ReactNode;
}
const MyComponent: React.FC<Props> = ({ foo, children }) => {
return (
<Box>
<Text>{`Something ${foo}`}</Text>
<Box>{children}</Box>
</Box>
);
};
export type MyComponentProps = Props;
export default MyComponent;
Custom Hook Rules
Hooks must be resilient, typed, and encapsulate their own side effects (like event listeners or storage sync).
- •Return Objects: Return objects (e.g., { value, setValue }) instead of tuples for better extensibility and readability.
- •Error Handling: Silent failure via try/catch is required for browser APIs (like sessionStorage) to prevent hydration crashes or SSR errors.
- •State Syncing: If interacting with browser APIs (window events, storage), ensure cleanup in useEffect returns (e.g., removeEventListener).
Example: Standard Hook
import { useState, useCallback, useEffect } from 'react';
export const useCustomFeature = <TValue>(initialValue: TValue) => {
const [value, setInternalValue] = useState<TValue>(initialValue);
// Hook logic, side effects, and try/catch blocks here
// ...
const setValue = useCallback((newValue: TValue) => {
try {
// Execute setter logic
setInternalValue(newValue);
} catch (error) {
console.error('Feature execution failed:', error);
}
}, []);
// Return object, not tuple
return { value, setValue };
};
Modularity & Feature Isolation
Fat components are strictly banned. UI components must be presentation-focused.
- •The
featuresDirectory: Domain-specific logic (e.g.,storage,auth,google-drive) must be encapsulated withinpackages/main/src/features/{feature-name}/. Expose hooks, types, and constants from here. Do not leak business logic intosrc/components. - •Client Wrappers: Keep Next.js Page components (
page.tsx) as thin Server Components whenever possible. Abstract heavy client-side logic into a separate Client Wrapper (e.g.,AuthClientWrapper.tsx) to minimize the"use client"boundary footprint. - •Component Splitting: Extract complex UI states (e.g., Authenticated vs. Unauthenticated views) into private, sibling components within the same file to keep the main render method clean and readable.
Approved Ecosystem & Dependencies
Avoid dependency bloat. Do not install micro-libraries for trivial logic that can be written in a few lines of standard TypeScript. When external libraries are necessary, strictly use the following approved, well-maintained tools:
- •Data Fetching & Async State: Use
@tanstack/react-queryfor all server state management, caching, and data synchronization. - •Schema Validation: Use
zodfor strict runtime type-checking of API payloads, environment variables, and complex form state. Trusting implicit types from external sources is banned. - •API Requests: Use
apisaucefor standardized API communication. - •Date Manipulation: Prefer native JavaScript
DateandIntl.DateTimeFormatfor simple parsing and formatting to avoid bundle bloat. If, and only if, complex date manipulation or timezone math is required, usedate-fns.
Reusable Component Architecture (MUI 7 Slots Pattern)
Reusable components must use the Slots & SlotProps pattern for maximum flexibility.
- •Imports: Use
InferSlotsFromSlotProps,SxPropsfrom@swiftpost/elysium/ui/typesandspreadSxfrom@swiftpost/elysium/utils/styles/sxProps. - •Interface Structure:
- •Define
SlotPropsinterface for internal elements (e.g.,root,footer). - •Main
Propsmust includeslots(optional overrides),slotProps(props for those slots), andsx(root style overrides).
- •Define
- •Style Merging: Never overwrite
sx. Always usespreadSxwithin an array to merge styles safely:sx={[{ ...baseStyles }, ...spreadSx(sx)]}. - •Base Name: Define a
const componentBaseName(kebab-case) for stable CSS class targeting. - •Optimization: Always wrap reusable UI components in
memo.
Example: Advanced Slot Pattern
import type { InferSlotsFromSlotProps, SxProps } from '@swiftpost/elysium/ui/types';
import { spreadSx } from '@swiftpost/elysium/utils/styles/sxProps';
import Box from '@swiftpost/elysium/ui/base/Box';
import { memo } from 'react';
const componentBaseName = 'custom-layout';
interface SlotProps {
root: { children: React.ReactNode; sx?: SxProps };
footer?: { children: React.ReactNode; sx?: SxProps };
}
interface Props {
slots?: Partial<InferSlotsFromSlotProps<SlotProps>>;
slotProps: SlotProps;
sx?: SxProps;
}
const CustomLayout: React.FC<Props> = ({ slots, slotProps, sx }) => {
const Root = slots?.root ?? Box;
const Footer = slots?.footer ?? Box;
return (
<Root
className={componentBaseName}
// Merge styles using array syntax and spreadSx utility
sx={[{ display: 'flex', flexDirection: 'column' }, ...spreadSx(sx), ...spreadSx(slotProps.root.sx)]}
>
{slotProps.root.children}
{slotProps.footer && (
<Footer className={`${componentBaseName}-footer`} sx={[...spreadSx(slotProps.footer.sx)]}>
{slotProps.footer.children}
</Footer>
)}
</Root>
);
};
export type CustomLayoutProps = Props;
export default memo(CustomLayout);
Styling, Theming & Responsive Design
- •
Mobile-First Approach: Strictly adhere to mobile-first design. Define base styles for mobile (xs) first, then use breakpoints to override for larger screens.
- •Example: width: { xs: '100%', md: '50%' }
- •
Static Theme Usage: Use staticTheme for values to avoid hook overhead.
- •Spacing: 0.5rem factor. spacing(2) = 1rem.
- •Breakpoints: xs: 340, sm: 600, md: 900, lg: 1200, xl: 1536.
- •
Typography: Font family defaults to var(--font-roboto).
Elysium UI Library Usage
The elysium package is the definitive source for all UI elements. It distinguishes between thin MUI wrappers and enhanced custom components.
Import Strategy
- •Base Components (MUI Wrappers): Import standard MUI components from
@swiftpost/elysium/ui/base/*.- •Note:
Typographyis renamed toText. Always importTextfrom@swiftpost/elysium/ui/base/Text.
- •Note:
- •Custom & Enhanced Components: Import specialized components directly from
@swiftpost/elysium/ui/*.
Key Enhanced Components
- •Link (
@swiftpost/elysium/ui/Link): * Integratesnext/linkwith MUI styling.- •Use
externalprop to bypassnext/link. - •Use
blankfortarget="_blank". - •Use
inheritStyleto inherit font properties from the parent.
- •Use
- •Image (
@swiftpost/elysium/ui/Image): * Smart wrapper aroundnext/image.- •Automatically falls back to a standard
<img>tag if the source is a simple string (url) without dimensions, preventing Next.js image errors.
- •Automatically falls back to a standard
- •ContentFittedStack (
@swiftpost/elysium/ui/ContentFittedStack): * Use for layout sections that need constrained max-width content centering.
Hooks
- •
useMediaQuery: Import from@swiftpost/elysium/ui/useMediaQuery. - •
useTheme: Import from@swiftpost/elysium/ui/useTheme.
Example: Elysium Composition
import { staticTheme } from '@/styles/staticTheme';
import Text from '@swiftpost/elysium/ui/base/Text'; // Base wrapper (Typography)
import ContentFittedStack from '@swiftpost/elysium/ui/ContentFittedStack'; // Custom layout
import Link from '@swiftpost/elysium/ui/Link'; // Enhanced Link
import Image from '@swiftpost/elysium/ui/Image'; // Enhanced Image
const MyFooter = () => (
<ContentFittedStack
component="footer"
minHeight={staticTheme.spacing(6)}
// "container" slot style override pattern
slotProps={{
container: { sx: { backgroundColor: 'grey.100' } }
}}
>
<Text>
Powered by <Link href="[https://example.com](https://example.com)" external blank>SwiftPost</Link>
</Text>
<Image source="/logo.png" alt="Logo" width={100} height={50} />
</ContentFittedStack>
);