diff --git a/playground/components/global/drupal-ce-container.ts b/playground/components/global/drupal-ce-container.ts
new file mode 100644
index 0000000..a2da3fd
--- /dev/null
+++ b/playground/components/global/drupal-ce-container.ts
@@ -0,0 +1,111 @@
+/**
+ * DrupalCeContainer Component
+ *
+ * A component that serves as a container for rendering Drupal custom elements.
+ * Supports two modes of operation:
+ *
+ * 1. Slot Mode - Using Vue slots for content
+ * 2. JSON Mode - Using content prop with array of elements
+ *
+ * @example Slot Mode Usage:
+ * ```vue
+ *
+ * Cell content before embed
+ * Some example embedded element.
+ * Cell content after embed
+ *
+ * ```
+ *
+ * @example JSON Mode Usage:
+ * ```vue
+ *
+ * ```
+ */
+
+import { h, defineComponent } from 'vue';
+import type { VNode } from 'vue';
+// useDrupalCe is auto-imported by Nuxt
+
+export default defineComponent({
+ name: 'DrupalCeContainer',
+
+ props: {
+ /**
+ * HTML tag to use for the container
+ * @default 'div'
+ */
+ tag: {
+ type: String,
+ default: 'div'
+ },
+
+ /**
+ * Array of custom elements to render (for JSON mode)
+ * Each element should have an 'element' property defining the component type
+ * @optional
+ */
+ content: {
+ type: Array,
+ default: null
+ }
+ },
+
+ // @todo: Remove once issue #332 is fixed.
+ // We manually inherit attrs below.
+ inheritAttrs: false,
+
+ /**
+ * @slot default - Content slot for custom elements in Slot mode
+ */
+
+ render(): VNode {
+ // useDrupalCe is auto-imported by Nuxt
+ const { renderCustomElements } = useDrupalCe();
+
+ // Determine which mode we're in - slot mode or JSON mode
+ const isJsonMode = Array.isArray(this.content);
+
+ // Get all props except 'tag', 'content', and 'element' to pass to the container
+ const containerProps = { ...this.$attrs };
+
+ // Do not render the "element" prop passed by renderCustomElements()
+ // @todo: Remove once issue #332 is fixed.
+ if ('element' in containerProps) {
+ delete containerProps.element;
+ }
+
+ let children;
+
+ if (isJsonMode && this.content) {
+ // JSON mode - render each element in the content array
+ children = this.content.map(item => renderCustomElements(item));
+ } else if (this.$slots.default) {
+ // Slot mode - use the default slot content
+ children = this.$slots.default();
+ } else {
+ // No content
+ children = [];
+ }
+
+ // Render the container with the appropriate tag and children.
+ return h(this.tag, containerProps, children);
+ }
+});
diff --git a/test/unit/components/drupal-ce-container.test.ts b/test/unit/components/drupal-ce-container.test.ts
new file mode 100644
index 0000000..30535dd
--- /dev/null
+++ b/test/unit/components/drupal-ce-container.test.ts
@@ -0,0 +1,251 @@
+// @vitest-environment nuxt
+import { describe, it, expect } from 'vitest'
+import { mountSuspended } from '@nuxt/test-utils/runtime'
+import { defineComponent } from 'vue'
+import { useNuxtApp } from "#imports"
+
+describe('DrupalCeContainer', () => {
+ // Register test components
+ const DrupalMedia = defineComponent({
+ name: 'DrupalMedia',
+ inheritAttrs: false,
+ props: {
+ id: String,
+ content: String
+ },
+ template: '
{{ content }}
'
+ })
+
+ // Register components globally
+ const app = useNuxtApp()
+ app.vueApp.component('DrupalMedia', DrupalMedia)
+ // Note: DrupalMarkup is already available in the playground
+
+ // Helper function to normalize HTML by removing whitespace between elements
+ const normalizeHtml = (html) => {
+ return html
+ .replace(/>\s+<') // Remove whitespace between tags
+ .replace(/\s+/g, ' ') // Replace multiple spaces with single space
+ .trim(); // Trim leading/trailing whitespace
+ }
+
+ it('renders with slot-based content', async () => {
+ const wrapper = await mountSuspended(defineComponent({
+ template: `
+
+
+ Cell content before embed
+
+ Cell content after embed
+
+
+ `
+ }))
+
+ const html = normalizeHtml(wrapper.html())
+
+ // Create a well-formatted expected HTML string for easy reading in the IDE
+ const expectedHtml = normalizeHtml(`
+ Cell content before embedSome example embedded element.
Cell content after embed
+ `)
+
+ // Assert the exact HTML structure
+ expect(html).toEqual(expectedHtml)
+ })
+
+ it('renders with JSON-based content', async () => {
+ const { renderCustomElements } = useDrupalCe()
+
+ const wrapper = await mountSuspended(defineComponent({
+ setup() {
+ return {
+ component: renderCustomElements({
+ element: 'drupal-ce-container',
+ tag: 'section',
+ class: 'test-container',
+ content: [
+ {
+ 'element': 'drupal-markup',
+ 'content': 'Cell content before embed'
+ },
+ {
+ 'element': 'drupal-media',
+ 'id': "123",
+ 'content': 'Some example embedded element.'
+ },
+ {
+ 'element': 'drupal-markup',
+ 'content': 'Cell content after embed'
+ }
+ ]
+ })
+ }
+ },
+ template: ''
+ }))
+
+ const html = normalizeHtml(wrapper.html())
+
+ // Create a well-formatted expected HTML string for easy reading in the IDE
+ // In JSON mode, DrupalMarkup components do add a wrapper div
+ const expectedHtml = normalizeHtml(`
+
+ Cell content before embed
+ Some example embedded element.
+ Cell content after embed
+
+ `)
+
+ // Assert the exact HTML structure
+ expect(html).toEqual(expectedHtml)
+ })
+
+ it('respects the default tag prop value', async () => {
+ const wrapper = await mountSuspended(defineComponent({
+ template: `
+
+
+ Test content
+
+
+ `
+ }))
+
+ const html = normalizeHtml(wrapper.html())
+
+ // Create a well-formatted expected HTML string for easy reading in the IDE
+ const expectedHtml = normalizeHtml(`
+ Test content
+ `)
+
+ // Assert the exact HTML structure
+ expect(html).toEqual(expectedHtml)
+ })
+
+ it('works with empty content', async () => {
+ const wrapper = await mountSuspended(defineComponent({
+ template: `
+
+
+ `
+ }))
+
+ const html = normalizeHtml(wrapper.html())
+
+ // Create a well-formatted expected HTML string for easy reading in the IDE
+ const expectedHtml = normalizeHtml(`
+
+ `)
+
+ // Assert the exact HTML structure
+ expect(html).toEqual(expectedHtml)
+ })
+
+ it('renders table with embedded content using slot syntax', async () => {
+ const wrapper = await mountSuspended(defineComponent({
+ template: `
+
+
+
+
+ First column
+
+
+ Media heading
+
+
+
+
+
+ `
+ }))
+
+ const html = normalizeHtml(wrapper.html())
+
+ // Create a well-formatted expected HTML string for easy reading in the IDE
+ const expectedHtml = normalizeHtml(`
+ | First column | Media headingMedia content |
+ `)
+
+ // Assert the exact HTML structure
+ expect(html).toEqual(expectedHtml)
+ })
+
+ it('renders table with embedded content using JSON syntax', async () => {
+ const { renderCustomElements } = useDrupalCe()
+
+ const wrapper = await mountSuspended(defineComponent({
+ setup() {
+ return {
+ component: renderCustomElements({
+ element: 'drupal-ce-container',
+ tag: 'table',
+ content: [
+ {
+ element: 'drupal-ce-container',
+ tag: 'tbody',
+ content: [
+ {
+ element: 'drupal-ce-container',
+ tag: 'tr',
+ content: [
+ {
+ element: 'drupal-ce-container',
+ tag: 'td',
+ content: [
+ {
+ element: 'drupal-markup',
+ content: 'First column'
+ }
+ ]
+ },
+ {
+ element: 'drupal-ce-container',
+ tag: 'td',
+ content: [
+ {
+ element: 'drupal-markup',
+ content: 'Media heading
'
+ },
+ {
+ element: 'drupal-media',
+ id: '123',
+ content: 'Media content'
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ })
+ }
+ },
+ template: ''
+ }))
+
+ const html = normalizeHtml(wrapper.html())
+
+ // Create a well-formatted expected HTML string for easy reading in the IDE
+ const expectedHtml = normalizeHtml(`
+
+
+
+ |
+ First column
+ |
+
+ Media heading
+ Media content
+ |
+
+
+
+ `)
+
+ // Assert the exact HTML structure
+ expect(html).toEqual(expectedHtml)
+ })
+
+})