Responsive Activities
Adapt to Discord layout mode, orientation, safe areas, locale, and thermal state.
An Activity can appear full screen, beside chat, on a mobile device, or in a compact Discord layout. Build from the available container and Discord context instead of assuming a normal website viewport.
#Start with safe-area CSS
Ludicord installs CSS variables from Discord's safe-area data:
1/* file: app/globals.css */2.controls {3 position: fixed;4 right: calc(1rem + var(--ludicord-safe-right));5 bottom: calc(1rem + var(--ludicord-safe-bottom));6}
Use safe areas on edges that can overlap Discord controls. Do not add the inset to every nested component, or padding will accumulate.
#Read numeric safe-area values
1import { useLudicordSafeArea } from "ludicord/discord";23const safeArea = useLudicordSafeArea();4canvas.resize({ top: safeArea.top, bottom: safeArea.bottom });
Prefer CSS for ordinary layout. Use the hook for canvases, physics viewports, or libraries that require numbers.
#React to Discord layout signals
1const layoutMode = useActivityLayoutMode();2const orientation = useOrientation();3const thermalState = useThermalState();4const locale = useDiscordLocale();
Values can be null outside Discord or before the event arrives. Build a reasonable CSS default first.
#Combine CSS and runtime data
Use media/container queries for width and runtime hooks for behavior that CSS cannot express.
1const thermal = useThermalState();2const effects = thermal === "critical" ? "low" : "high";34return <GameCanvas effects={effects} />;
#Mobile interaction checklist
- Keep touch targets at least 44px in both dimensions.
- Avoid hover-only information and actions.
- Test the smallest supported Activity width.
- Let text wrap and grow at 200% browser text size.
- Put essential controls above the bottom safe area.
- Pause expensive animation when
useActivityLifecycle().visibleis false. - Respect
prefers-reduced-motion. - Do not require a hardware keyboard.
#Minimized UI
app/minimize.tsx is connected automatically. Keep it lightweight and focused on the one fact or action useful while the full Activity is hidden.
1import { Minimize } from "ludicord";23export default function MinimizedActivity() {4 return <Minimize heading="Game night" message="Your round is still running." />;5}
The minimized surface should not duplicate the full app or create a second navigation system.
#Next step
Move trusted work to API routes, the first server-side convention in the learning path.