+
+`;
diff --git a/packages/fuselage/src/components/Accordion/index.ts b/packages/fuselage/src/components/Accordion/index.ts
index cadd5bc406..fb3ec96a24 100644
--- a/packages/fuselage/src/components/Accordion/index.ts
+++ b/packages/fuselage/src/components/Accordion/index.ts
@@ -1,2 +1,5 @@
-export * from './Accordion';
-export * from './AccordionItem';
+export { default as Accordion, type AccordionProps } from './Accordion';
+export {
+ default as AccordionItem,
+ type AccordionItemProps,
+} from './AccordionItem';
diff --git a/packages/fuselage/src/components/Box/StylingBox.tsx b/packages/fuselage/src/components/Box/StylingBox.tsx
new file mode 100644
index 0000000000..b18fd4bb4b
--- /dev/null
+++ b/packages/fuselage/src/components/Box/StylingBox.tsx
@@ -0,0 +1,34 @@
+import type { cssFn } from '@rocket.chat/css-in-js';
+import { cloneElement } from 'react';
+import type { ReactElement } from 'react';
+
+import { useArrayLikeClassNameProp } from '../../hooks/useArrayLikeClassNameProp';
+import type { Falsy } from '../../types/Falsy';
+import type { StylingProps } from './stylingProps';
+import { useStylingProps } from './useStylingProps';
+
+export type StylingBoxProps = {
+ children: ReactElement<{ className?: string }>;
+ className?: string | cssFn | (string | cssFn | Falsy)[];
+} & Partial;
+
+/**
+ * Merges its `StylingProps` props into a `className` prop passed to a child element.
+ *
+ * This is intended to be used as a wrapper for components that accept styling props but don't need the weight of the
+ * `Box` component prop handling internally.
+ */
+export const StylingBox = ({ children, ...props }: StylingBoxProps) => {
+ const propsWithStringClassName = useArrayLikeClassNameProp(props);
+ propsWithStringClassName.className = [
+ children.props.className,
+ propsWithStringClassName.className,
+ ]
+ .filter(Boolean)
+ .join(' ');
+ const propsWithoutStylingProps = useStylingProps(propsWithStringClassName);
+
+ return cloneElement(children, propsWithoutStylingProps);
+};
+
+export default StylingBox;
diff --git a/packages/fuselage/src/components/Box/index.ts b/packages/fuselage/src/components/Box/index.ts
new file mode 100644
index 0000000000..b2f4651178
--- /dev/null
+++ b/packages/fuselage/src/components/Box/index.ts
@@ -0,0 +1,2 @@
+export { default } from './Box';
+export { default as StylingBox, type StylingBoxProps } from './StylingBox';
diff --git a/packages/fuselage/src/components/Box/index.tsx b/packages/fuselage/src/components/Box/index.tsx
deleted file mode 100644
index a3e93ac535..0000000000
--- a/packages/fuselage/src/components/Box/index.tsx
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './Box';
diff --git a/packages/fuselage/src/helpers/composeClassNames.ts b/packages/fuselage/src/helpers/composeClassNames.ts
index 704b1fccb4..8ac964b71c 100644
--- a/packages/fuselage/src/helpers/composeClassNames.ts
+++ b/packages/fuselage/src/helpers/composeClassNames.ts
@@ -1,3 +1,6 @@
+import { Falsy } from '../types/Falsy';
+import { exhaustiveCheck } from './exhaustiveCheck';
+
const withPrefix = (prefix?: string) => (modifier: string) =>
prefix ? `${prefix}--${modifier}` : modifier;
@@ -45,3 +48,95 @@ export const composeClassNames =
return [prefix, classNames].filter(Boolean).join(' ');
};
+
+type CxArg =
+ | Falsy
+ | string
+ | { [className: string]: boolean | string | number };
+
+/**
+ * Composes class names into a single string based on flags.
+ *
+ * Any falsy value passed as an argument will be ignored.
+ *
+ * If a string is passed, it will be used as a class name.
+ *
+ * If a dictionary object is passed, its keys will be used as class names and its values will be used as modifiers --
+ * unless the value is a boolean, in which case the key will be used as a class name if the value is `true`.
+ *
+ * @example
+ * ```ts
+ * cx('a', false, 'b', null, { c: true, d: false, e: 'f', f: 1 }); // 'a b c e-f f-1'
+ * ```
+ *
+ * @param args a sequence of falsy values, strings and dictionary objects containing the class names
+ * @returns a space-separated string of class names
+ */
+export const cx = (...args: CxArg[]): string =>
+ args
+ .flatMap((arg) => {
+ if (!arg) {
+ return [];
+ }
+
+ if (typeof arg === 'string') {
+ return arg;
+ }
+
+ if (typeof arg === 'object') {
+ return Object.entries(arg).flatMap(([className, value]) => {
+ if (typeof value === 'boolean') {
+ return value ? className : [];
+ }
+
+ return `${className}-${value}`;
+ });
+ }
+
+ return exhaustiveCheck(arg);
+ })
+ .join(' ');
+
+/**
+ * Composes class name modifiers under a class name into a single class names' string based on flags.
+ *
+ * This function returns a function similar to `cx`, with the difference it handles the arguments as class name
+ * modifiers (based on BEM CSS conventions). However, it's recommended to use `cxx` as a curried function.
+ *
+ * @example
+ * ```ts
+ * cxx('z')('a', false, 'b', null, { c: true, d: false, e: 'f', f: 1 }); // 'z z--a z--b z--c z--e-f z--f-1'
+ * ```
+ * @param className the class name
+ * @param args a sequence of falsy values, strings and dictionary objects containing the modifiers
+ * @returns a space-separated string of class names
+ */
+export const cxx =
+ (className: string) =>
+ (...args: CxArg[]): string => {
+ const classNames = args.flatMap((arg) => {
+ if (!arg) {
+ return [];
+ }
+
+ if (typeof arg === 'string') {
+ return `${className}--${arg}`;
+ }
+
+ if (typeof arg === 'object') {
+ return Object.entries(arg).flatMap(([modifier, value]) => {
+ if (typeof value === 'boolean') {
+ return value ? `${className}--${modifier}` : [];
+ }
+
+ return `${className}--${modifier}-${value}`;
+ });
+ }
+
+ return exhaustiveCheck(arg);
+ });
+
+ classNames.unshift(className);
+
+ return classNames.join(' ');
+ };
diff --git a/packages/fuselage/src/helpers/exhaustiveCheck.ts b/packages/fuselage/src/helpers/exhaustiveCheck.ts
new file mode 100644
index 0000000000..0f32cd27f5
--- /dev/null
+++ b/packages/fuselage/src/helpers/exhaustiveCheck.ts
@@ -0,0 +1,30 @@
+/**
+ * Utility function to check exhaustiveness of a switch statement or if-else statements.
+ *
+ * In TypeScript, variables going through a sequence of conditionals, like cases in switch statements or if-else
+ * statements, the variable type is incrementally narrowed as the conditionals are evaluated until it reaches the
+ * `never` type, meaning all possible cases have been covered. This function is used to check, at compilation time, that
+ * all possible cases have been covered (i.e. if the conditional checks were exhaustive) and should only be called in
+ * unreachable blocks of code.
+ *
+ * @example
+ * ```ts
+ * declare const value: 'foo' | 'bar';
+ * switch (value) {
+ * case 'foo':
+ * // ...
+ * break;
+ * case 'bar':
+ * // ...
+ * break;
+ * default: // should be unreachable
+ * exhaustiveCheck(value); // `value` type should be `never`, otherwise the compilation fails
+ * }
+ * ```
+ *
+ * @param _ the value which type should be `never`
+ * @throws {Error} will always throw an error if it's reached
+ */
+export const exhaustiveCheck = (_: never): never => {
+ throw new Error('Exhaustive check failed');
+};