To install dependencies:
npm installTo run:
npm run devIf you just need to develop a component outside of its context within a workflow, you can use Storybook:
npm run storybook
If you need to test your component within the larger context of a workflow, you'll need to setup Zenpayroll + GWS-Flows.
- Follow setup instructions for Zenpayroll and start local dev server
- In another tab run the following commands in Zenpayroll directory(these generate necessary partner account for
gws-flows) :
bundle exec rails runner "DevAccountCreator.new.create_dev_accounts" rake partners_api:dev_setup_for_gws_onboarding - In another tab run the following commands in Zenpayroll directory(these generate necessary partner account for
- Follow setup instructions in the readme for GWS Flows
- in SDK, Run
npm run dev:setup; if that fails, follow the instructions below to setup manually:- Run
npm link ../gws-flows/node_modules/reactin sdk folder - this is needed to avoid duplicate react instances in local development due to using npm/yarn link - In GWS-Flows project folder, run
yarn link -r ../embedded-react-sdk
- Run
- In SDK, run
npm run dev
Now your local changes appear in GWS Flows.
To see the SDK running in GWS Flows, visit it locally and choose React SDK (New company) or React SDK (Company Onboarded) under Select a Type and click Create Demo
Components and Flows will be shown at the top of the page in a nav. Company Components will automatically appear as they are added.
Select a Flow or Component to view it
Translations are stored in /src/i18n.
The are broken out by locale (e.g. en), then by namespace, which is usually the name of the component they are used in. The source of truth is the en locale - the rest will be autogenerated by translation service.
After writing the new translations, run npm run interface to generate the types.
In your components, first use useI18n hook, which takes in component namespace and loads appropriate resource file into dictionary. After that use the useTranslation hook to access the translations in that component and any child components.
Follow these conventions when creating or modifying translation keys:
Use camelCase for all translation key names:
{
"pageTitle": "Federal Tax Information",
"federalEinLabel": "Federal EIN",
"federalEinDescription": "Your company's Federal Employer Identification Number",
"continueCta": "Continue"
}Use consistent suffixes to indicate the purpose of each key:
Cta- Call-to-action buttons (e.g.,submitCta,continueCta,cancelCta)Label- Form field labels (e.g.,firstNameLabel,emailLabel)Description- Help text or descriptions (e.g.,federalEinDescription)Title- Page or section headings (e.g.,pageTitle,sectionTitle)Placeholder- Input placeholders (e.g.,emailPlaceholder)Error- Error messages (e.g.,requiredFieldError,invalidEmailError)
Group related keys using nested objects for better organization:
{
"validations": {
"firstName": "First name is required",
"lastName": "Last name is required",
"email": "Email address is required and must be valid",
"address": {
"street1": "Street address is required",
"city": "City is required",
"state": "State is required"
}
},
"labels": {
"openMenu": "Open menu",
"menuLabel": "Menu"
},
"alerts": {
"progressSaved": "Your progress has been saved",
"errorEncountered": "There was a problem with your submission"
}
}Common grouping patterns:
validations- Form validation error messageslabels- Accessibility labels and field labelsalerts- Alert and notification messagesicons- Icon accessibility labelstable- Table-related labels (column headers, actions)
Only use snake_case when required by external contracts:
- API Enum Values - When keys match backend API responses:
{
"onboardingStatus": {
"admin_onboarding_incomplete": "Admin-onboarding Incomplete",
"self_onboarding_invited": "Self-onboarding: Invited"
}
}- i18next Pluralization - Required by i18next for plural forms:
{
"priority_one": "{{count}}st",
"priority_two": "{{count}}nd",
"priority_few": "{{count}}rd",
"priority_other": "{{count}}th"
}- Programmatic Identifiers - When used as flow/step identifiers that match API:
{
"stepTitles": {
"add_addresses": "Add company addresses",
"federal_tax_setup": "Company federal tax information"
}
}Good:
{
"pageTitle": "Employee Profile",
"continueCta": "Continue",
"cancelCta": "Cancel",
"validations": {
"firstName": "First name is required",
"emailFormat": "Email must be valid format"
},
"labels": {
"personalInfo": "Personal Information"
}
}Avoid:
{
"page_title": "Employee Profile", // ❌ Don't use snake_case
"ContinueCTA": "Continue", // ❌ Don't capitalize suffix
"validation_first_name": "First name...", // ❌ Don't use snake_case
"first_name_validation": "First name...", // ❌ Poor grouping
"lbl_personal_info": "Personal Info" // ❌ Don't abbreviate
}Block components are focused, reusable components that serve specific functionality. They follow these patterns:
- Single Purpose: Each component should generally handle a specific task (e.g., list, form, etc.)
- Base Component: Uses BaseComponent for consistent behavior and error handling
- Compound Pattern: Exposes subcomponents (Head, List, Actions) for flexibility and composition
Flow components compose block components and other flow components together using state machines to manage transitions. They follow these patterns:
- State Management: Uses the Flow component with a state machine to handle transitions
- Component Composition: Can compose both block components (e.g., list → form) and other flow components
- Naming: Suffix with "Flow" (e.g.,
DocumentSigner,Locations)
For example, EmployeeOnboardingFlow composes both block components (profile, taxes) and other flow components (document signer) to create a complete onboarding experience.
You can run the test suite locally with the following command:
npm run testWhen creating a pull request, use the provided PR template (.github/PULL_REQUEST_TEMPLATE.md). Here are the guidelines:
Use conventional commits format for your PR title. PR titles are validated by CI. When a release is prepared with npm run release, commit history (which uses squash-merge PR titles) determines the version bump and populates the changelog — so PR title format directly affects how releases are documented.
Note: Commitlint runs in two places: as a pre-commit hook (via husky) validating individual commit messages, and as a CI check validating the PR title, which becomes the squash merge commit message.
PR titles determine the version bump and changelog entry when a release is prepared, following semver.org specification:
During 0.x.x (pre-1.0 development):
| Commit Type | Version Bump | When to Use |
|---|---|---|
feat |
MINOR (0.1.0 → 0.2.0) | New features or functionality |
fix |
PATCH (0.1.0 → 0.1.1) | Bug fixes |
feat! or fix! (with !) |
MINOR (0.1.0 → 0.2.0) | Breaking changes* |
docs, chore, refactor, test, ci, style, perf, build, revert |
No bump | Non-functional changes |
*Per semver spec, during 0.x.x the API is considered unstable, so breaking changes bump MINOR instead of MAJOR. The jump to 1.0.0 will be an intentional decision when we're ready to declare a stable API.
feat: add new component- New feature → MINOR bumpfix: resolve issue with form validation- Bug fix → PATCH bumpfeat!: redesign JSX component props- Breaking change → MINOR bump (during 0.x.x)chore: update dependencies- Maintenance → no version bumprefactor: simplify state machine logic- Code refactoring → no version bumpdocs: update README- Documentation → no version bump
For work tied to a Jira ticket, include the ticket in the scope so the title still follows conventional commits:
feat(SDK-123): add payroll blocker alerts- MINOR bumpfix(SDK-456): correct date formatting- PATCH bump
- Summary: Brief description of what the PR does and why
- Changes: Briefly list only the most important changes (high-level only; do not enumerate every file-level change)
- Demo: Screenshots or screen recordings showing the changes (when applicable)
- Related: Links to Jira tickets, Figma designs, or related PRs
- Testing: Instructions for reviewers to test the changes
- Keep PRs focused on a single concern when possible
- Include visual demos (screenshots/videos) for UI changes
- Link to relevant Jira tickets using the format
[SDK-XXX](https://gustohq.atlassian.net/browse/SDK-XXX) - Add Storybook stories for new components
- Include unit tests for new functionality
- Run
npm run testandnpm run lintbefore submitting
All commits must follow commitlint enforced format: type(scope?): subject #scope is optional; multiple scopes are supported (current delimiter options: "/", "\" and ",")
Following types are currently defined:
'build',
'chore',
'ci',
'docs',
'feat',
'fix',
'perf',
'refactor',
'revert',
'style',
'test'
Read more about conventional commits here
You can find documentation on building with the Gusto Embedded React SDK in the docs directory within this repo.
Releases are prepared with release-it, which reads commits since the last release, proposes a semver bump, updates package.json and CHANGELOG.md, and creates a release branch ready for review.
From a clean main branch:
npm run releaserelease-it will:
- Show you the commits since the last release grouped by type
- Propose a version based on conventional commit prefixes (
feat→ MINOR,fix→ PATCH,feat!/fix!→ MINOR during 0.x.x) - Ask you to confirm or override the target version
- Bump
package.json, updatepackage-lock.json, and prepend a new section toCHANGELOG.md - Commit the changes and check out a
chore/release-<version>branch automatically
Then push and open a PR:
git push -u origin chore/release-<version>
gh pr create --title "chore: release <version>"Trigger the Prepare Release workflow from the Actions UI (Run workflow). It accepts an optional version override; if left blank it auto-detects from commits. The workflow creates the branch, commits the changes, and opens a PR automatically.
Publishing happens automatically. When a chore: release commit lands on main and all CI checks pass, the Publish to NPM workflow runs automatically and publishes to NPM.
If you need to trigger a publish manually (e.g. CI was re-run after the auto-trigger fired, or something went wrong), you can still run the workflow manually from the Actions UI (Run workflow).