diff --git a/.changeset/shiny-suits-occur.md b/.changeset/shiny-suits-occur.md
new file mode 100644
index 00000000..b30bf6e3
--- /dev/null
+++ b/.changeset/shiny-suits-occur.md
@@ -0,0 +1,6 @@
+---
+'@asgardeo/browser': patch
+'@asgardeo/react': patch
+---
+
+Fix issues in Language Dropdown & expose emoji resolving utils
diff --git a/packages/browser/src/index.ts b/packages/browser/src/index.ts
index efbcdedf..7baac241 100644
--- a/packages/browser/src/index.ts
+++ b/packages/browser/src/index.ts
@@ -67,3 +67,4 @@ export {default as getActiveTheme} from './theme/getActiveTheme';
export {default as handleWebAuthnAuthentication} from './utils/handleWebAuthnAuthentication';
export {default as http} from './utils/http';
+export {default as resolveEmojiUrisInHtml} from './utils/v2/resolveEmojiUrisInHtml';
diff --git a/packages/browser/src/utils/v2/resolveEmojiUrisInHtml.ts b/packages/browser/src/utils/v2/resolveEmojiUrisInHtml.ts
new file mode 100644
index 00000000..9c8f8dcc
--- /dev/null
+++ b/packages/browser/src/utils/v2/resolveEmojiUrisInHtml.ts
@@ -0,0 +1,49 @@
+/**
+ * Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com).
+ *
+ * WSO2 LLC. licenses this file to you under the Apache License,
+ * Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import {extractEmojiFromUri, isEmojiUri} from '@asgardeo/javascript';
+
+/**
+ * Resolves `emoji:` URIs in an HTML string.
+ *
+ * Handles two forms:
+ * - `
` → `🐯`
+ * - Bare `emoji:🐯` text references → `🐯`
+ *
+ * @param html - The HTML string that may contain `emoji:` URIs.
+ * @returns The HTML string with all `emoji:` URIs replaced.
+ */
+const resolveEmojiUrisInHtml = (html: string): string => {
+ const withEmojiImages: string = html.replace(
+ /
]*)src="(emoji:[^"]+)"([^>]*)\/?>/gi,
+ (_match: string, pre: string, src: string, post: string): string => {
+ const emoji: string = extractEmojiFromUri(src);
+ if (!emoji) {
+ return _match;
+ }
+ const altMatch: RegExpMatchArray | null = (pre + post).match(/alt="([^"]*)"/i);
+ const label: string = altMatch ? altMatch[1] : emoji;
+ return `${emoji}`;
+ },
+ );
+ return withEmojiImages.replace(/emoji:([^\s"<>&]+)/g, (_: string, rest: string): string =>
+ isEmojiUri(`emoji:${rest}`) ? rest : `emoji:${rest}`,
+ );
+};
+
+export default resolveEmojiUrisInHtml;
diff --git a/packages/react/src/components/presentation/LanguageSwitcher/LanguageSwitcher.tsx b/packages/react/src/components/presentation/LanguageSwitcher/LanguageSwitcher.tsx
index 399f407f..1a86d55f 100644
--- a/packages/react/src/components/presentation/LanguageSwitcher/LanguageSwitcher.tsx
+++ b/packages/react/src/components/presentation/LanguageSwitcher/LanguageSwitcher.tsx
@@ -17,7 +17,7 @@
*/
import {resolveLocaleDisplayName, resolveLocaleEmoji} from '@asgardeo/browser';
-import {FC, ReactElement, ReactNode, useMemo} from 'react';
+import {FC, ReactElement, ReactNode, useEffect, useMemo} from 'react';
import BaseLanguageSwitcher, {LanguageOption, LanguageSwitcherRenderProps} from './BaseLanguageSwitcher';
import useFlowMeta from '../../../contexts/FlowMeta/useFlowMeta';
import useTranslation from '../../../hooks/useTranslation';
@@ -85,12 +85,13 @@ const LanguageSwitcher: FC = ({children, className}: Lang
const {currentLanguage} = useTranslation();
const availableLanguageCodes: string[] = meta?.i18n?.languages ?? [];
- const effectiveLanguageCodes: string[] = useMemo(() => {
- const fallbackCodes: string[] = availableLanguageCodes.length > 0 ? availableLanguageCodes : [currentLanguage];
-
- // Ensure the current language is always resolvable for display label and emoji.
- return Array.from(new Set([currentLanguage, ...fallbackCodes]));
- }, [availableLanguageCodes, currentLanguage]);
+ // Only fall back to the detected browser language when the server returns no configured languages.
+ // Do NOT inject currentLanguage unconditionally — a browser locale like "en-GB" must not appear
+ // in the picker when the server only supports "en-US".
+ const effectiveLanguageCodes: string[] = useMemo(
+ () => (availableLanguageCodes.length > 0 ? availableLanguageCodes : [currentLanguage]),
+ [availableLanguageCodes, currentLanguage],
+ );
const languages: LanguageOption[] = useMemo(
() =>
@@ -103,6 +104,13 @@ const LanguageSwitcher: FC = ({children, className}: Lang
[effectiveLanguageCodes],
);
+ // If the detected language isn't supported by the server, fall back to the first available language.
+ useEffect(() => {
+ if (availableLanguageCodes.length > 0 && !availableLanguageCodes.includes(currentLanguage)) {
+ switchLanguage(availableLanguageCodes[0]);
+ }
+ }, [availableLanguageCodes, currentLanguage, switchLanguage]);
+
const handleLanguageChange = (language: string): void => {
if (language !== currentLanguage) {
switchLanguage(language);
diff --git a/packages/react/src/components/presentation/auth/AuthOptionFactory.tsx b/packages/react/src/components/presentation/auth/AuthOptionFactory.tsx
index fc9feb21..5a607c58 100644
--- a/packages/react/src/components/presentation/auth/AuthOptionFactory.tsx
+++ b/packages/react/src/components/presentation/auth/AuthOptionFactory.tsx
@@ -25,8 +25,7 @@ import {
EmbeddedFlowEventTypeV2 as EmbeddedFlowEventType,
createPackageComponentLogger,
resolveFlowTemplateLiterals,
- extractEmojiFromUri,
- isEmojiUri,
+ resolveEmojiUrisInHtml,
ConsentPurposeDataV2 as ConsentPurposeData,
ConsentPromptDataV2 as ConsentPromptData,
ConsentDecisionsV2 as ConsentDecisions,
@@ -36,6 +35,7 @@ import {
import {css} from '@emotion/css';
import DOMPurify from 'dompurify';
import {cloneElement, CSSProperties, ReactElement} from 'react';
+import useTheme from '../../../contexts/Theme/useTheme';
import {UseTranslation} from '../../../hooks/useTranslation';
import Consent from '../../adapters/Consent';
import {getConsentOptionalKey} from '../../adapters/ConsentCheckboxList';
@@ -69,23 +69,6 @@ const logger: ReturnType = createPackageCom
* - `
` → `X`
* - Any remaining `emoji:X` text occurrences → `X`
*/
-const resolveEmojiUrisInHtml = (html: string): string => {
- const withEmojiImages: string = html.replace(
- /
]*)src="(emoji:[^"]+)"([^>]*)\/?>/gi,
- (_match: string, pre: string, src: string, post: string): string => {
- const emoji: string = extractEmojiFromUri(src);
- if (!emoji) {
- return _match;
- }
- const altMatch: RegExpMatchArray | null = (pre + post).match(/alt="([^"]*)"/i);
- const label: string = altMatch ? altMatch[1] : emoji;
- return `${emoji}`;
- },
- );
- return withEmojiImages.replace(/emoji:([^\s"<>&]+)/g, (_: string, rest: string): string =>
- isEmojiUri(`emoji:${rest}`) ? rest : `emoji:${rest}`,
- );
-};
/** Ensures rich-text content (including all inner elements from the server) always word-wraps. */
const richTextClass: string = css`
@@ -217,6 +200,8 @@ const createAuthComponentFromFlow = (
variant?: any;
} = {},
): ReactElement | null => {
+ const {theme} = useTheme();
+
const key: string | number = options.key || component.id;
/** Resolve any remaining {{t()}} or {{meta()}} template expressions in a string at render time. */
@@ -421,6 +406,12 @@ const createAuthComponentFromFlow = (
case EmbeddedFlowComponentType.Block: {
if (component.components && component.components.length > 0) {
+ const formStyles: CSSProperties = {
+ display: 'flex',
+ flexDirection: 'column',
+ gap: `calc(${theme.vars.spacing.unit} * 2)`,
+ };
+
const blockComponents: any = component.components
.map((childComponent: any, index: any) =>
createAuthComponentFromFlow(
@@ -441,7 +432,7 @@ const createAuthComponentFromFlow = (
.filter(Boolean);
return (
-
);
diff --git a/packages/react/src/components/primitives/FormControl/FormControl.styles.ts b/packages/react/src/components/primitives/FormControl/FormControl.styles.ts
index 6441f725..858a4c69 100644
--- a/packages/react/src/components/primitives/FormControl/FormControl.styles.ts
+++ b/packages/react/src/components/primitives/FormControl/FormControl.styles.ts
@@ -42,7 +42,6 @@ const useStyles = (
const formControl: string = css`
text-align: start;
font-family: ${theme.vars.typography.fontFamily};
- margin-bottom: calc(${theme.vars.spacing.unit} * 2);
`;
const helperText: string = css`
diff --git a/packages/react/src/contexts/FlowMeta/FlowMetaProvider.tsx b/packages/react/src/contexts/FlowMeta/FlowMetaProvider.tsx
index 3c7fbd09..8ef264ee 100644
--- a/packages/react/src/contexts/FlowMeta/FlowMetaProvider.tsx
+++ b/packages/react/src/contexts/FlowMeta/FlowMetaProvider.tsx
@@ -18,20 +18,11 @@
import {FlowMetadataResponse, FlowMetaType, getFlowMetaV2} from '@asgardeo/browser';
import {I18nBundle, TranslationBundleConstants} from '@asgardeo/i18n';
-import {
- FC,
- PropsWithChildren,
- ReactElement,
- RefObject,
- useCallback,
- useContext,
- useEffect,
- useRef,
- useState,
-} from 'react';
+import {FC, PropsWithChildren, ReactElement, RefObject, useCallback, useEffect, useRef, useState} from 'react';
import FlowMetaContext from './FlowMetaContext';
import useAsgardeo from '../Asgardeo/useAsgardeo';
-import I18nContext, {I18nContextValue} from '../I18n/I18nContext';
+import {I18nContextValue} from '../I18n/I18nContext';
+import useI18n from '../I18n/useI18n';
export interface FlowMetaProviderProps {
/**
@@ -69,7 +60,7 @@ const FlowMetaProvider: FC> = ({
enabled = true,
}: PropsWithChildren): ReactElement => {
const {baseUrl, applicationId} = useAsgardeo();
- const i18nContext: I18nContextValue = useContext(I18nContext);
+ const i18nContext: I18nContextValue = useI18n();
const [meta, setMeta] = useState(null);
const [isLoading, setIsLoading] = useState(false);
@@ -90,14 +81,19 @@ const FlowMetaProvider: FC> = ({
setError(null);
try {
- const result: FlowMetadataResponse = await getFlowMetaV2({baseUrl, id: applicationId, type: FlowMetaType.App});
+ const result: FlowMetadataResponse = await getFlowMetaV2({
+ baseUrl,
+ id: applicationId,
+ language: i18nContext?.currentLanguage,
+ type: FlowMetaType.App,
+ });
setMeta(result);
} catch (err: unknown) {
setError(err instanceof Error ? err : new Error(String(err)));
} finally {
setIsLoading(false);
}
- }, [enabled, baseUrl, applicationId]);
+ }, [enabled, baseUrl, applicationId, i18nContext?.currentLanguage]);
const switchLanguage: (language: string) => Promise = useCallback(
async (language: string): Promise => {
diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts
index a47abe7b..37d1b495 100644
--- a/packages/react/src/index.ts
+++ b/packages/react/src/index.ts
@@ -289,6 +289,15 @@ export {
http,
getActiveTheme,
navigate,
+ resolveEmojiUrisInHtml,
+ isEmojiUri,
+ EMOJI_URI_SCHEME,
+ extractEmojiFromUri,
+ resolveMeta,
+ resolveFlowTemplateLiterals,
+ countryCodeToFlagEmoji,
+ resolveLocaleDisplayName,
+ resolveLocaleEmoji,
// Export `v2` models and types as first class citizens since they are
// going to be the primary way to interact with embedded flows moving forward.
EmbeddedFlowComponentTypeV2 as EmbeddedFlowComponentType,