docs/published/handbook/engineering/conventions/frontend-coding.md
In this page you can find a collection of guidelines, style suggestions, and tips for making contributions to the codebase.
Our frontend webapp is written with Kea and React as two separate layers. Kea is used to organise the app's data for rendering (we call this the data or state layer), and React is used to render the computed state (this is the view or template layer).
We try to be very explicit about this separation, and avoid local React state wherever possible, with exceptions for the lib/ folder. Having all our data in one layer makes for code that's easier to test, and observe. Basically, getting your data layer right is hard enough. We aim to not make it harder by constraining your data to a DOM-style hierarchy.
Hence the explicit separation between the data and view layers.
logic.useState or useEffect to store local state. It's false convenience. Take the extra 3 minutes and change it to a logic early on in the development.lib/ folder, which might be rendered hundreds of times on a page with different sets of data. Still feel free to write a logic for a complicated lib/ component when needed.export const DashboardMenu = () => <div />), and avoid default exports.camelCase variables in JS, snake_case in Python).searchResults vs data).dashboardLogic)DashboardMenu).Props (DashboardLogicProps & DashboardMenuProps).ts file according to its main export: DashboardMenu.ts or DashboardMenu.tsx or dashboardLogic.ts or Dashboard.scss. Pay attention to the case.index.ts, styles.css, and other generic names, even if this is the only file in a directory.sceneLogic.SceneExport, containing the scene's root logic and its React component.tabId: string prop. It's strongly recommended to key your logic with this tabId.tabAwareScene() function to your scene's logic. This catches bugs when mounting the logic from somewhere without the tabId prop.urlToAction and actionToUrl, use tabAwareUrlToAction and tabAwareActionToUrl. Try to only only use them on the scene's logic, not in any deeper logics.useAttachedLogic(dataNoteLogic(propsFromComponent), mySceneLogic({ tabId })) to attach any logic to a scene logic. It'll persist until the scene's logic is unmounted, surviving React component remounts.breadcrumbs selector in your scene's logic. The last breadcrumb controls the title and the icon, the one before that controls the back button. If there are more breadcrumbs, they will be ignored.subscriptions and propsChanged sparingly, only if you can't find any other way. These have a high chance of leading to messy, cyclic or slow data flows.urlToAction in your scene's logic (e.g. insightSceneLogic), and never deeper down in e.g. propertyFilterLogic.MyBlogComponent.tsx import MyBlogComponent.scss.DashboardMenu { put everything here }.DashboardMenu__modal to keep things namespaced.pnpm storybook locally.Sync note: This file is also copied to posthog/posthog/.claude/commands/conventions.md for Claude Code. When updating this file, please also update the copy there.