{"id":24755183,"url":"https://github.com/gravity-ui/onboarding","last_synced_at":"2025-10-11T01:31:28.923Z","repository":{"id":182761157,"uuid":"658867211","full_name":"gravity-ui/onboarding","owner":"gravity-ui","description":null,"archived":false,"fork":false,"pushed_at":"2025-09-20T18:32:18.000Z","size":1450,"stargazers_count":16,"open_issues_count":4,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-09-20T20:28:56.574Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/gravity-ui.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2023-06-26T16:47:30.000Z","updated_at":"2025-09-20T18:32:19.000Z","dependencies_parsed_at":"2023-07-21T10:04:46.660Z","dependency_job_id":"d4d4fa66-4d77-4329-83b7-3c2f88877434","html_url":"https://github.com/gravity-ui/onboarding","commit_stats":null,"previous_names":["gravity-ui/onboarding"],"tags_count":74,"template":false,"template_full_name":"gravity-ui/package-example","purl":"pkg:github/gravity-ui/onboarding","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gravity-ui%2Fonboarding","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gravity-ui%2Fonboarding/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gravity-ui%2Fonboarding/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gravity-ui%2Fonboarding/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gravity-ui","download_url":"https://codeload.github.com/gravity-ui/onboarding/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gravity-ui%2Fonboarding/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279003899,"owners_count":26083639,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-10T02:00:06.843Z","response_time":62,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-01-28T12:36:54.455Z","updated_at":"2025-10-11T01:31:28.917Z","avatar_url":"https://github.com/gravity-ui.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# @gravity-ui/onboarding \u0026middot; [![npm package](https://img.shields.io/npm/v/@gravity-ui/onboarding)](https://www.npmjs.com/package/@gravity-ui/onboarding) [![CI](https://img.shields.io/github/actions/workflow/status/gravity-ui/onboarding/.github/workflows/ci.yml?label=CI\u0026logo=github)](https://github.com/gravity-ui/onboarding/actions/workflows/ci.yml?query=branch:main)\n\nCreate hint based onboarding scenarios and manage promo activities in your service.\nUse with React, any other frameworks or vanilla JS.\n\n- **Small**. Onboarding ~4Kb, promo-manager ~8kb. Zero dependencies\n- **Well tested**. ~80% test coverage\n- **Small code footprint**. Create onboarding scenario config and connect each step to UI with couple lines of code.\n- **No UI**. Use your own components\n- Good TypeScript support\n\n# Table of Contents\n\n- [Package contents](#package-contents)\n- [Onboarding guide](#onboarding-guide)\n  - [How to use onboarding](#how-to-use-onboarding)\n  - [Onboarding configuration](#onboarding-configuration)\n  - [Plugins](#onboarding-plugins)\n  - [Events](#events)\n- [Promo manager](#promo-manager)\n  - [How to use promo manager](#how-to-use-promo-manager)\n  - [Condition and constraints](#condition-and-constraints)\n  - [JSON config](#json-config)\n  - [Onboarding integration](#onboarding-integration)\n\n## Install\n\n```shell\nnpm i @gravity-ui/onboarding\n```\n\n# Package contents\n\nThis package contains 2 tools:\n\n- Onboarding - tool for creating hint based onboarding for service users. Create presets with steps, bind to elements and call methods. Onboarding will keep user progress and calculate next hint to show.\n\n- Promo manager - tool for managing any promo activities you push into user: banners, informers, advertisements of new features, UX surveys, educational popup. Put all promos in config and specify the conditions. Promo manager will keep user progress and calculate next promo to show.\n\n# Onboarding guide\n\nYou can try it out in [playground](https://stackblitz.com/edit/vitejs-vite-77hphtee?file=src%2Fonboarding%2Flib.ts)\n\n## How to use onboarding\n\n\u003cdetails\u003e\n  \u003csummary\u003eBasic react example\u003c/summary\u003e\n\n```typescript jsx\n// onboarding/lib.ts\nimport {createOnboarding, createPreset} from '@gravity-ui/onboarding';\n\nexport const {\n  useOnboardingStep,\n  useOnboardingPreset,\n  useOnboardingHint,\n  useOnboarding,\n  controller,\n} = createOnboarding({\n  config: {\n    presets: {\n    // createPreset - wrapper for better type inference\n      todoListFirstUsage: createPreset({\n        name: '',\n        steps: [\n          createStep({\n            slug: 'createTodoList',\n            name: 'create-todo-list',\n            description: 'Click button to create todo list',\n          }),\n          /* other scanario steps */\n        ],\n      }),\n    },\n  },\n  // onboarding state from backend\n  baseState: () =\u003e {/* ... */},\n  getProgressState: () =\u003e {/* ... */},\n  // save new onboarding state to backend\n  onSave: {\n    state: (state) =\u003e {/* ... */},\n    progress: (progress) =\u003e {/* ... */},\n  },\n});\n```\n\n```typescript jsx\n// Hint.tsx\nimport { useCallback } from 'react';\nimport { Popup } from '@gravity-ui/uikit';\nimport { useOnboardingHint } from './lib.ts';\n\nexport const OnboardingHint = () =\u003e {\n  const { anchorRef, hint, open, onClose } = useOnboardingHint();\n\n  const handleClose = useCallback(() =\u003e {\n    onClose();\n  }, [onClose]);\n\n  return (\n    \u003c\u003e\n      \u003cPopup\n        anchorElement={anchorRef.current}\n        open={open}\n        onOpenChange={handleClose}\n      \u003e\n        \u003ch3\u003e{hint?.step.name}\u003c/h3\u003e\n        {hint?.step.description}\n      \u003c/Popup\u003e\n    \u003c/\u003e\n  );\n};\n```\n\n```typescript jsx\n// todo-list.tsx\nimport { useOnboardingStep } from './onboarding/lib';\n\nexport const SomeFeature = () =\u003e {\n  const { pass, ref } = useOnboardingStep('createTodoList');\n\n  return (\n    \u003cButton\n      onClick={() =\u003e {\n        pass();\n        handleAddTodoList();\n      }}\n      ref={ref}\n      // ...\n    \u003e\n      \"Add new list\"\n    \u003c/Button\u003e\n  );\n\n};\n```\n\nIf you dont have access to target element you can use `useOnboardingStepBySelector`\n\n```typescript jsx\n// todo-list.tsx\nimport {useOnboardingStepBySelector} from '../todo-list-onboarding.ts';\n\nconst ref = useRef()\nuseOnboardingStepBySelector({\n  element: ref.current,\n  selector: '.deep_nested_element',\n  step: 'createFirstIssue'\n});\n\nreturn (\n  \u003cButton\n    onClick={() =\u003e {\n      pass();\n      handleAddTodoList();\n    }}\n    ref={ref}\n    // ...\n  \u003e\n    \"Add new list\"\n  \u003c/Button\u003e\n);\n```\n\n\n\u003c/details\u003e\n\n## Onboarding configuration\n\n\n### Onboarding options\nYou can configure onboarding\n\n```typescript jsx\nconst onboardingOptions = {\n  config: {\n    presets: {/**/},\n  },\n  globalSwitch: 'off', // optional.  turn off onboarding globally. For example in some entire env \n  ignoreUnknownPresets: true, // optional. will not thorow error for unknown presets\n  baseState: {/**/}, // initial state for current user\n  getProgressState: () =\u003e {/**/}, // function to load user progress\n  customDefaultState: {/**/}, // optional. will apply this value for user with no base state\n  onSave: {\n    // functions to save user state\n    state: (state) =\u003e {/**/},\n    progress: (progress) =\u003e {/**/},\n  },\n  showHint: (state) =\u003e {}, // optional. function to show hint. Only for vanilla js usage\n  logger: { // optional. you can specify custom logger\n    level: 'error' as const,\n    logger: {\n      debug: () =\u003e {/**/},\n      error: () =\u003e {/**/},\n    },\n  },\n  debugMode: true, // optional. true will show a lot of debug messages. Recommended for dev environment\n  plugins: [/**/], // optional. you can use existing plugins or write your own\n  // optional. you can subscribe to onboarding events\n  hooks: {\n    showHint: ({preset, step}) =\u003e {/**/},\n    stepPass: ({preset, step}) =\u003e {/**/},\n    addPreset: ({preset}) =\u003e {/**/},\n    beforeRunPreset: ({preset}) =\u003e {/**/},\n    runPreset: ({preset}) =\u003e {/**/},\n    finishPreset: ({preset}) =\u003e {/**/},\n    beforeSuggestPreset: ({preset}) =\u003e {/**/},\n    beforeShowHint: ({stepData}) =\u003e {/**/},\n    stateChange: ({state}) =\u003e {/**/},\n    hintDataChanged: ({state}) =\u003e {/**/},\n    closeHint: ({hint, eventSource}) =\u003e {/**/},\n    closeHintByUser: ({hint, eventSource}) =\u003e {/**/},\n    init: () =\u003e {/**/},\n    wizardStateChanged: ({wizardState}) =\u003e {/**/},\n    applyDefaultState: ({wizardState}) =\u003e {/**/},\n  },\n};\n```\n\n## Common preset configuration\nFor default preset you can specify properties:\n\n```typescript jsx\nconst onboardingOptions = {\n  config: {\n    presets: {\n      // you can use goPrevStep and goNextStep in steps\n      createProject: createPreset(({goPrevStep, goNextStep}) =\u003e ({\n        // preset name should be unique\n        name: 'Creating project', // text for user\n        type: 'default', // optional. 'default'(default value) | 'interlal' | 'combined'\n        visibility: 'visible', // optional. 'visible'(defaule value) | 'initialHidden' | 'alwaysHidden';\n        description: '', // optional, text for user\n        steps: [\n          {\n            slug: 'openBoard', // step slug must be unique across all presets\n            name: '', // text to show in popup\n            description: '', // text to show in popup\n            placement: 'top', // optional. Hint placement for step\n            passMode: 'onAction', // optional. 'onAction'(default value) | 'onShowHint' - trigger step pass on hint show\n            hintParams: {\n              // you can use theese actions in Hint component to display buttons\n              actions: [\n                {\n                  children: 'Go back',\n                  view: 'action' as const,\n                  onClick: () =\u003e {\n                    goNextStep()\n                  },\n                },\n                {\n                  children: 'Go next',\n                  view: 'action' as const,\n                  onClick: () =\u003e {\n                    goNextStep()\n                  },\n                },\n              ],\n            }, // optional. any custom properties for hint\n            closeOnElementUnmount: false, // optional. default valeue - false. Will close hint when element umnounts. 'True' not reccomended in general^ but may me helpful for some corners\n            passRestriction: 'afterPrevious', // optional. afterPrevious will block pass step is previous not passed\n            hooks: {\n              // optional\n              onStepPass: () =\u003e {/**/},\n              onCloseHint: () =\u003e {/**/},\n              onCloseHintByUser: () =\u003e {/**/},\n            },\n          },\n        ],\n        hooks: {\n          // optional\n          onBeforeStart: () =\u003e {/**/},\n          onStart: () =\u003e {/**/},\n          onEnd: () =\u003e {/**/},\n        },\n      })),\n    },\n  },\n};\n```\n\n### Combined presets\n\nFor combined preset you need to add internal preset to config and specify `pickPreset` function.\n\n```typescript jsx\nimport {createInternalPreset, createCombinedPreset, createOnboarding} from '@gravity-ui/onboarding';\n\ncreateOnboarding({\n    config: {\n        presets: {\n            //  you need to add internal presets. Here it is internal1 and internal2\n            internal1:  createInternalPreset({\n                name: 'Internal2',\n                type: 'internal' as const,\n                steps: [/* ... */]\n            }),\n            internal2:  createInternalPreset({\n                name: 'Internal2',\n                type: 'internal' as const,\n                steps: [/* ... */],\n            }),\n            // combined preset has no steps\n            combined: createCombinedPreset({\n                name: 'combined',\n                type: 'combined' as const,\n                // pickPreset calls on preset start and resolve combined preset to specific internal preset\n                pickPreset: () =\u003e {\n                    if(someCondition) {\n                        return 'internal1'\n                    }\n                    \n                    return 'internal2'\n                },\n                internalPresets: ['internal1', 'internal2'],\n            }),\n        },\n    },\n});\n```\n\n You can find more examples in [test data](https://github.com/gravity-ui/onboarding/blob/main/src/tests/utils.ts#L71)\n\n## Events\n\nYou can use event system. Available events: `showHint`, `stepPass`, `addPreset`, `beforeRunPreset`, `runPreset`, `finishPreset`, `beforeSuggestPreset`, `stepElementReached`, `beforeShowHint`, `stateChange`, `hintDataChanged`, `closeHint`, `init`, `wizardStateChange`\n\n```typescript jsx\ncontroller.events.subscribe('beforeShowHint', callback);\n```\n\nCallbacks can be async. Some events can cancel target action: `stepElementReached`, `beforeShowHint`, `beforeSuggestPreset`\n\n```typescript jsx\ncontroller.events.subscribe('stepElementReached', async () =\u003e {\n    /* ... */\n    if(someCondition) {\n        // forbid show hint\n        return false\n    }\n});\n```\n\n## Onboarding plugins\n\nYou can use plugins\n\n- **MultiTabSyncPlugin** - synchronizes the closing of the hint and the state (experimentally between tabs). The user will not have to hack one hint several times if he opened the page in several tabs.\n  State synchronization also synchronizes the state of completed/not completed scenarios and the wizard, but **may lead to memory leaks**. Disabled by default, enable at your own risk\n- **WizardPlugin** - useful if you have a wizard where the user can see his scenarios and can start them. Plugin\n  - loads progress at startup if the wizard is open\n  - shows hint when showing wizard if its element is visible\n  - closes hint when closing wizard\n  - closes hint when starting preset and erases progress for unfinished presets\n  - expands wizard when preset is finished\n- **PromoPresetPlugin** - adds logic around presets with `visibility: 'alwaysHidden'`. Such presets are considered promo presets and additional logic is attached to them.\n  - hints of promo presets are not displayed while wizard is open\n  - hints of regular presets are not displayed while wizard is hidden\n  - (optional) toggles enabled state in user state if hint needs to be displayed\n  - (optional) toggles enabled state in user state when issuing (suggestPresetOnce) preset\n\nExample:\n```typescript jsx\nimport {createOnboarding} from '@gravity-ui/onboarding';\nimport {\n  MultiTabSyncPlugin,\n  PromoPresetsPlugin,\n  WizardPlugin,\n} from '@gravity-ui/onboarding/dist/plugins';\n\nconst {controller} = createOnboarding({\n  /* ... */\n  plugins: [\n    new MultiTabSyncPlugin({\n      enableStateSync: false, // Experimantal. Default - false(recommended). Sync all onboarding state.\n      enableCloseHintSync: true, // closes hont in all browser tabs,\n      changeStateLSKey: 'onboarding.plugin-sync.changeState', // localStorage key for state sync\n      closeHintLSKey: 'onboarding.plugin-sync.closeHint', // localStorage key for close hint in all tabs\n    }),\n    new PromoPresetsPlugin({\n      turnOnWhenShowHint: true, // Default - true. Force to turn on onboarding, when promo hint should be shown\n      turnOnWhenSuggestPromoPreset: true, // Default - true. Force to turn on onboarding, when promo preset suggested\n    }),\n    new WizardPlugin(),\n  ],\n});\n```\n\n---\n\nYou can write your own plugin\n\n```typescript jsx\nimport {createOnboarding} from '@gravity-ui/onboarding';\nimport {WizardPlugin} from '@gravity-ui/onboarding/dist/plugins';\n\nconst myPlugin = {\n  apply: (onboarding) =\u003e {\n    /**\n     * Do something with onboarding controller\n     * For exampe subscribe on event\n     *  onboarding.events.subscribe('init', () =\u003e {});\n     */\n  },\n};\n\nconst {controller} = createOnboarding({\n  /**/\n  plugins: [new WizardPlugin(), myPlugin],\n});\n```\n\n# Promo manager\n\n## How to use promo-manager\n### 1. Init promo manager and setup the progress update\n\n```typescript jsx\n// promo-manager.ts\n\nimport { createPromoManager } from '@gravity-ui/onboarding/dist/promo-manager';\nimport { ShowOnceForPeriod } from '@gravity-ui/onboarding/dist/promo-manager/helpers';\n\nexport const { controller, usePromoManager, useActivePromo } = createPromoManager({\n    config: {\n        promoGroups: [{\n            slug: 'poll',\n            conditions: [ShowOnceForPeriod({month: 1})],\n            promos: [\n                {\n                    slug: 'issuePoll',\n                    conditions: [ShowOncePerMonths(6)],\n                    meta: {...}\n                },\n            ],\n        }],\n    },\n    progressState: () =\u003e {/* ... */},\n    getProgressState: () =\u003e {/* ... */},\n    onSave: {\n        progress: (state) =\u003e () =\u003e {/* ... */},\n    },\n});\n```\n\n### 2. Trigger promo in your component\n\n```typescript jsx\n// TriggerExample.tsx\n\nimport { usePromoManager } from './promo-manager';\n\nconst { status, requestStart, skipPromo } = usePromoManager('issuePoll');\n\nuseMount(() =\u003e {\n    requestStart();\n});\n\nuseUnmount(() =\u003e {\n    skipPromo();\n});\n\nif(status === 'active') {\n    // allowed to run. Do something\n}\n```\n\n## Condition and constraints\nYou can use conditions for each promo. Or use constraints to set limitations between promos.\n\nPromo could be started only if\n- All promo conditions returns true\n- All promo group condition returns true\n- All constraints passed\n\n```typescript jsx\nimport {\n    ShowOnceForSession,\n    ShowOnceForPeriod,\n    MatchUrl,\n    LimitFrequency\n} from '@gravity-ui/onboarding/dist/promo-manager/helpers';\n\nconst groupOfPolls = {\n    slug: 'groupOfPolls',\n    promos: [\n        {slug: 'somePoll', conditions: [ShowOnceForPeriod({month: 1})]},\n        {slug: 'pollForPageWithparam', conditions: [\n            MatchUrl('param=value'),\n            ShowOnceForPeriod({month: 5})\n        ]}\n    ],\n}\n\nconst groupOfHints = {\n    slug: 'groupOfHints',\n    conditions: [ShowOnceForSession()],\n    promos: [\n        {slug: 'someHint'},\n        {slug: 'hintForSpecificPage', conditions: [\n            MatchUrl('/folder/\\\\w{5}/page$'),\n        ]},\n    ],\n}\n\nconst {controller} = createPromoManager({\n    config: {\n        constraints: [\n            LimitFrequency({\n                slugs: ['somePoll', 'groupOfHints'], // can use promos slugs and group slugs\n                interval: {days: 1},\n            })\n        ],\n        promoGroups: [groupOfHints, groupOfPolls]\n    },\n});\n\n```\n---\nYou can write your own conditions.\n```typescript jsx\n\nconst user = {/**/} // get user from state\nconst usersWithLanguage = (language) =\u003e user.language = language;\n\nconst promo = {slug: 'somePoll', conditions: [usersWithLanguage('english')]};\n```\n---\n## Promo manager events\n\nPromo manager can run promo on events.\n\n```typescript\nconst promo = {\n    slug: 'promo1',\n    conditions: [],\n    trigger: {on: 'someCustomEvent', timeout: 1000}\n}\n\nconst {controller} = createPromoManager({\n    config: {\n        promoGroups: [{\n            slug: 'group',\n            conditions: [],\n            promos: [promo],\n        }]\n    },\n});\n\ncontroller.sendEvent('someCustomEvent')\n```\n\nYou can also use `UrlEventPlugin` to run promos on specific url. Promo will run when user opens page.\n\n```typescript\nconst {controller} = createPromoManager({\n    config: {\n        promoGroups: [\n            {\n                slug: '1',\n                promos: [\n                    {\n                        slug: 'promo1',\n                        conditions: [MatchUrl('/folder/\\\\w{5}/page$'),],\n                        trigger: {on: 'pageOpened', timeout: 2000},\n                    },\n                ],\n            },\n        ],\n    },\n    plugins: [new UrlEventsPlugin({eventName: 'pageOpened'})],\n})\n```\n\n## JSON config\n\nYou can define conditions, constraints as JSON serializable objects. So you can take config from json, parse it and use. It can be useful for editing config without rebuild project and release.\n\n\n```typescript\n\nconst usersWithLanguage = (language) =\u003e user.language = language;\n\nconst {controller} = createPromoManager({\n    config: { // config section now can be parsed from json\n        constraints: [\n            {\n                helper: 'LimitFrequency',\n                args: [{\n                    slugs: ['somePoll', 'groupOfHints'], // can use promos slugs and group slugs\n                    interval: {days: 1},\n                }],\n            },\n        ],\n        promoGroups: [{\n            slug: 'groupOfPolls',\n            promos: [\n                {\n                    slug: 'somePoll',\n                    conditions: [{\n                        helper: 'ShowOnceForPeriod',\n                        args: [{month: 1}]\n                    }]\n                },\n                {\n                    slug: 'pollForPageWithparam',\n                    conditions: [\n                        {\n                            helper: 'MatchUrl',\n                            args: ['param=value']\n                        },\n                        {\n                            helper: 'usersWithLanguage',\n                            args: ['English']\n                        },\n                    ]\n                }\n            ],\n        }]\n    },\n    conditionHelpers: {\n        usersWithLanguage,\n    },\n});\n```\n\n## Onboarding integration\n\nYou can use onboarding with `PromoPresetsPlugin` to show advertising and educational hints. You can use promo manager to limit frequency and set constraint with other promo in service.\n\n```typescript\nimport {\n    createOnboarding\n} from \"./index\";\n\nconst {controller: onboardingController} = createOnboarding({\n    config: {\n        presets: {\n            coolNewFeature: {\n                name: 'Cool feature',\n                visibility: 'alwaysHidden',\n                steps: [/**/]\n            },\n            coolNewFeature2: {\n                name: 'Cool feature2',\n                visibility: 'alwaysHidden',\n                steps: [/**/],\n            }\n        }\n    },\n    plugins: [new PromoPresetsPlugin(),]\n   /**/ \n})\n\nconst {controller} = createPromoManager({\n    ...testOptions,\n    config: {\n        promoGroups: [\n            {\n                slug: 'hintPromos',\n                conditions: [ShowOnceForSession()], // only 1 promo hint for session\n                promos: [\n                    {\n                        slug: 'coolNewFeature', // slug = onboarding preset name \n                        conditions: [/**/], //  you can add additional conditions\n                    },\n                ],\n            },\n        ],\n    },\n    onboarding: {\n        getInstance: () =\u003e onboardingController,\n        groupSlug: 'hintPromos',\n    },\n});\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgravity-ui%2Fonboarding","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgravity-ui%2Fonboarding","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgravity-ui%2Fonboarding/lists"}