Vue 3 composable adapter for @polargold/pg-frontend-core — the shared, framework-agnostic utility layer.
Vue 3 composables built on top of @polargold/pg-frontend-core: reactive/lifecycle-aware wrappers
(useApi, useLocalStorage, useDateTime, useCurrency, useColorGradient, useBreakpoint), router/permission
composables (useKeyboardEvent, useRouterGo, useRouterPush, usePermission, useRoutePermission,
useCheckTokenValidity), validation helpers (useValidationMethods for Vuelidate, useJoiValidation for Joi),
DOM/accessibility composables (useFocusTrap, useClickOutside), and a few standalone ones (useLocalization,
useLoadingState, useAppVersionCheck).
Extracted from the near-duplicate composables found across luv-ui-tools, bws-ui-tools, and the
luv-autoq-frontend/ama-deep/data-sheet-portal app repos during the September 2026 frontend tooling
audit. Brand-specific composables (useSeminarType, useHomeLink, useSnackbarMethods/useSnackbarRetrieval)
are not here — they live in each brand's own *-ui-library package instead. See
What belongs here.
nvm use && npm install
# Configure access to the polargold registry (once, globally). Token in Bitwarden under "[NPM Registry] pg-admin"
npm config set -- //npm.white-lan.de/:_authToken=${token}
cd your-project/
echo "@polargold:registry=https://npm.white-lan.de" >> .npmrc
npm install -S @polargold/pg-frontend-vue
Peer dependencies: vue ^3.5. The rest are optional peer dependencies, only needed for the composable that
uses them — install yourself if so, npm won't pull them in automatically:
| Peer dependency | Needed for |
|---|---|
vue-router (^4 or ^5) |
useRouterGo, useRouterPush, useRoutePermission |
@vuelidate/core ^2.0.3 |
useValidationMethods |
i18next ^26.4.1 |
useLocalization |
joi is not a dependency of this package at all, despite useJoiValidation — it only appears as a
TypeScript type (import type Joi from "joi"), which is erased at compile time. Verified in the built bundle:
zero runtime reference. Bring your own joi install and construct the schema yourself; this composable just
runs it.
npm run lint
npm run lint:fix
npm run type-check
npm run vitest:run
npm run test
npm run docs:build
npm run docs:serve
npm run vite:build
| Service | URL |
|---|---|
| Package registry | Package registry |
| TypeDoc | pg-frontend-vue.polargold.dev |
Vue-specific composables only — anything using ref/computed/onMounted/useRouter/etc. Framework-agnostic
logic (classes, plain functions, error types) belongs in @polargold/pg-frontend-core instead, which this
package depends on and wraps. Brand-specific composables belong in that brand's *-ui-library package instead.
That last rule cuts both ways: useFocusTrap/useClickOutside/useLocalization were found living in
luv-ui-library/bws-ui-library (duplicated across both) despite having zero brand coupling — genuinely
generic code sitting in the wrong package, the opposite mistake from useIconPosition (brand-specific-looking
code that was actually too narrow to be generic). When re-scanning for anything missed, check *-ui-library
composables, not just *-ui-tools — that's exactly where these three were hiding.
useApiPorted unchanged from luv-ui-tools/bws-ui-tools — reactive isLoading/apiData wrapper around an async
callback, throwing ApiCallError (re-exported from pg-frontend-core) on failure.
import { useApi } from "@polargold/pg-frontend-vue";
const { isLoading, apiData, callApi } = useApi({
callback: () => apiLibrary.myFeature.getItems(),
onError: (error) => console.error(error),
});
useLocalStorageThin wrapper around pg-frontend-core's LocalStorage class.
import { useLocalStorage } from "@polargold/pg-frontend-vue";
const { write, read, remove } = useLocalStorage("accessToken", { mode: import.meta.env.MODE });
useDateTime, useCurrency, useColorGradientPorted from luv-ui-tools/bws-ui-tools — thin wrappers around pg-frontend-core's DateFormatter/
CurrencyFormatter/ColorGradient.
import { useDateTime, useCurrency, useColorGradient } from "@polargold/pg-frontend-vue";
const { internationalDate } = useDateTime();
const { transformCurrency } = useCurrency(); // or useCurrency("USD", "en-US")
const { generateColorGradient } = useColorGradient();
useBreakpointPorted from luv-ui-tools/bws-ui-tools — a window-width-driven set of breakpoint computeds.
Not every brand/project uses the same responsive breakpoints, so the thresholds
(BreakpointDefinitionValue: sm/md/lg/xl/2xl) are overridable — pass a partial object to change just
the ones that differ from the defaults. Calling useBreakpoint() with no arguments behaves exactly as before.
import { useBreakpoint } from "@polargold/pg-frontend-vue";
const { isMdAndUp } = useBreakpoint(); // uses the shared defaults (sm: 480, md: 640, lg: 1024, xl: 1200, 2xl: 1440)
// This brand's own Tailwind config uses different breakpoints:
const { isMdAndUp: isMdAndUpCustom } = useBreakpoint({ sm: 320, md: 768, lg: 1024 });
useKeyboardEventReplaces the copies in luv-autoq-frontend, ama-deep, bws-checkout-service-spa. Exposes raw
addEventListener/removeEventListener rather than wiring its own onMounted/onUnmounted — wire it to
whatever lifecycle fits your use case.
import { onMounted, onUnmounted } from "vue";
import { useKeyboardEvent } from "@polargold/pg-frontend-vue";
const { addEventListener, removeEventListener } = useKeyboardEvent(() => close(), ["Escape"]);
onMounted(addEventListener);
onUnmounted(removeEventListener);
useRouterGoReplaces the copies in luv-autoq-frontend, ama-deep, data-sheet-portal. The hardcoded
console.group-logging + redirect-to-error-page behavior from the ama-deep/data-sheet-portal copies
becomes an opt-in onError callback.
import { useRouterGo } from "@polargold/pg-frontend-vue";
import router from "@/router";
import { ROUTE } from "@/router/routeDefinitions";
const { routerGo } = useRouterGo({
onError: async () => router.push({ name: ROUTE.PUBLIC_ERROR_PAGE.NAME }),
});
useRouterPushReplaces the copies in luv-autoq-frontend, ama-deep, data-sheet-portal (ama-deep/data-sheet-portal
byte-identical; luv-autoq-frontend's also calls Sentry.captureException). Used internally by
useRoutePermission for its own permission-redirects — see its onNavigationError option.
import { useRouterPush } from "@polargold/pg-frontend-vue";
import { ROUTE } from "@/router/routeDefinitions";
const { routerPush } = useRouterPush({
onError: async (error) => {
console.error("Navigation failed", error);
await router.push({ name: ROUTE.PUBLIC_PAGE_NOT_FOUND.NAME });
},
});
usePermissionReplaces the copies in luv-autoq-frontend, ama-deep, data-sheet-portal. Takes getUserRoles/isAdmin
callbacks instead of importing an app-specific useAuthUserStore() directly.
import { usePermission } from "@polargold/pg-frontend-vue";
import { useAuthUserStore } from "@/stores/AppStartUp/AuthUserStore";
const authUserStore = useAuthUserStore();
const { hasAnyPermission, hasAllPermissions } = usePermission({
getUserRoles: () => authUserStore.roles,
isAdmin: () => authUserStore.isAdminUser,
});
useRoutePermissionReplaces the copies in ama-deep/data-sheet-portal (byte-identical between the two).
Bug fix carried over: requires
isAuthUserLoaded—luv-autoq-frontend's copy of this composable was missing that guard entirely (found during the audit), so it isn't one of the source copies here.
import { useRoutePermission } from "@polargold/pg-frontend-vue";
import { useAuthUserStore } from "@/stores/AppStartUp/AuthUserStore";
import { ROUTE } from "@/router/routeDefinitions";
import router from "@/router";
const authUserStore = useAuthUserStore();
const { initialize } = useRoutePermission({
getUserRoles: () => authUserStore.roles,
isAdmin: () => authUserStore.isAdminUser,
isAuthUserLoaded: () => authUserStore.authUserIsLoaded,
notAllowedRouteName: ROUTE.PUBLIC_PAGE_NOT_ALLOWED.NAME,
// optional - called if the permission-redirect itself fails (see useRouterPush above)
onNavigationError: async (error) => router.push({ name: ROUTE.PUBLIC_ERROR_PAGE.NAME }),
});
await initialize();
Route-level access is still declared the same way as before, via route.meta.hasAnyPermission /
hasAllPermissions / excludeRoles / customRedirectName (this package augments Vue Router's RouteMeta
type for these).
useCheckTokenValidityReplaces the copies in ama-deep/data-sheet-portal (byte-identical between the two).
import { useCheckTokenValidity } from "@polargold/pg-frontend-vue";
import { useAccessTokenStore } from "@/stores/AppStartUp/AccessTokenStore";
import { useOpenIdAuthenticationStore } from "@/stores/AppStartUp/OpenIdAuthenticationStore";
const accessTokenStore = useAccessTokenStore();
const authStore = useOpenIdAuthenticationStore();
const { initialize, reset } = useCheckTokenValidity({
isTokenValid: () => accessTokenStore.tokenIsValid,
onInvalid: () => authStore.logout(),
});
useValidationMethodsReplaces a copy byte-identical between luv-autoq-frontend and bws-checkout-service-spa — found during a
follow-up review pass, not in the original audit. Requires @vuelidate/core (optional peer dependency).
import useVuelidate from "@vuelidate/core";
import { required } from "@vuelidate/validators";
import { useValidationMethods } from "@polargold/pg-frontend-vue";
const state = reactive({ name: "" });
const $v = useVuelidate({ name: { required } }, state);
const { validate, hasErrorMessage, getErrorMessage } = useValidationMethods();
const hasError = await validate($v); // resets, validates, returns $v.value.$error
useJoiValidationThe Joi-flavored sibling to useValidationMethods — company convention (pg-api-guidelines-validation) names
both Vuelidate (single forms) and Joi (assembled/aggregate data) as standard. Generalized from
luv-autoq-frontend's useValidationStore: its app-specific field-name typing becomes a plain string, and
its direct useI18n() import becomes a translate callback, so this package doesn't need a vue-i18n
dependency. joi itself is not a runtime dependency of this package — see
Peer dependencies above.
import Joi from "joi";
import { useI18n } from "vue-i18n";
import { useJoiValidation } from "@polargold/pg-frontend-vue";
const { t } = useI18n();
const schema = Joi.object({ name: Joi.string().required() });
const formData = reactive({ name: "" });
const { validate, debouncedValidate, hasErrors, fieldHasError, getTranslatedErrorMessage } = useJoiValidation({
validationSchema: schema,
dataToValidate: () => formData,
translate: t, // vue-i18n's t, or anything else - your choice
});
await validate();
useFocusTrapReplaces a copy byte-identical between luv-ui-library and bws-ui-library — found during a follow-up
re-scan that checked the *-ui-library component packages too, not just *-ui-tools. Fully generic, so it
belongs here instead. focusableSelector is an optional override if a container's focusable elements aren't
covered by the sensible default list.
import { useFocusTrap } from "@polargold/pg-frontend-vue";
const modalRef = ref<HTMLElement>();
const { activate, deactivate } = useFocusTrap(modalRef);
useClickOutsidePorted from luv-ui-library, found during the same re-scan. Fully generic. Structured like useFocusTrap
(explicit activate/deactivate).
import { useClickOutside } from "@polargold/pg-frontend-vue";
const menuRef = ref<HTMLElement>();
const { activate, deactivate } = useClickOutside([menuRef], () => closeMenu());
useLocalizationReplaces a copy byte-identical between luv-ui-library and bws-ui-library — found during the same re-scan.
Requires i18next (optional peer dependency).
import { useLocalization } from "@polargold/pg-frontend-vue";
const { translate } = useLocalization();
translate("greeting", "Hello"); // falls back to "Hello" (or the key itself) if "greeting" doesn't exist
useLoadingStateReplaces the copy in luv-autoq-frontend. More general than useApi's built-in isLoading (tied to one
callback) — useful for multi-step flows where the loading state doesn't map to a single async call.
import { useLoadingState } from "@polargold/pg-frontend-vue";
const { isLoading, activateLoadingState, deactivateLoadingState } = useLoadingState();
useAppVersionCheckGeneralized from luv-autoq-frontend's copy — the standard "a new version is available, please reload"
pattern (interval + tab-visibility + window-focus triggers). Its hardcoded /version.json fetch, direct Pinia
store reads/writes, and direct @sentry/vue error reporting all become caller-supplied options.
import { useAppVersionCheck } from "@polargold/pg-frontend-vue";
import { useAppVersionStore } from "@/stores/appVersionStore";
const appVersionStore = useAppVersionStore();
const { initialize } = useAppVersionCheck({
getCurrentVersion: async () => {
try {
const response = await fetch(`/version.json?t=${Date.now()}`, { cache: "no-store" });
return (await response.json()).version;
} catch (err) {
Sentry.captureException(err);
return undefined;
}
},
getLoadedVersion: () => appVersionStore.loadedVersion,
setLoadedVersion: (version) => appVersionStore.setLoadedVersion(version),
isUpdateAlreadyFlagged: () => appVersionStore.updateAvailable,
onUpdateAvailable: () => appVersionStore.flagUpdateAvailable(),
});
await initialize();
ApiCallError / LocalStorageErrorRe-exported from @polargold/pg-frontend-core for convenience, so a Vue app consuming only this package
doesn't need a separate pg-frontend-core import just for error handling.
Every symbol is re-exported through src/composables/index.ts and through src/main.ts — nothing is
auto-discovered. Follow this for anything new.
Same as pg-frontend-core — everything pushed as a tag is type-checked, linted, tested, has its TypeDoc
built, and is published to the internal package registry automatically. Versioning is manual:
npm version [major | minor | patch | x.x.x]
polargold GmbH, Lilienstraße 5-9 / Semperhaus C, 20095 Hamburg