diff --git a/packages/cxl-lumo-styles/src/icons.js b/packages/cxl-lumo-styles/src/icons.js
index 67742374..a35fb45d 100644
--- a/packages/cxl-lumo-styles/src/icons.js
+++ b/packages/cxl-lumo-styles/src/icons.js
@@ -53,6 +53,9 @@ $documentContainer.innerHTML = `
+
+
+
diff --git a/packages/cxl-ui/scss/cxl-course-card.scss b/packages/cxl-ui/scss/cxl-course-card.scss
new file mode 100644
index 00000000..a03b1e4f
--- /dev/null
+++ b/packages/cxl-ui/scss/cxl-course-card.scss
@@ -0,0 +1,241 @@
+@use "~@conversionxl/cxl-lumo-styles/scss/mq";
+
+:host {
+ display: flex;
+ position: relative;
+ height: max-content;
+ box-sizing: border-box;
+ min-height: 300px;
+ font-size: var(--lumo-font-size-s);
+ padding: var(--lumo-space-m) var(--lumo-space-l);
+ background: var(--lumo-tint);
+ border: 1px solid var(--lumo-contrast-10pct);
+ border-radius: var(--lumo-border-radius-l);
+ box-shadow: var(--lumo-box-shadow-xs);
+ break-inside: avoid;
+ transform: translateZ(0); // CSS columns @see https://stackoverflow.com/a/55110789/35946
+
+ // @see https://github.com/conversionxl/aybolit/pull/293
+ --video-background: hsla(355.8, 74.7%, 48%, 0.03); // --lumo-primary-color-3pct does not exist
+ --training-background: hsla(0, 0%, 10%, 0.03); // --lumo-shade-3pct does not exist
+ --playbook-background: hsla(213, 100%, 62%, 0.03); // No similar base color exists
+
+ // Container / Media queries
+ @media #{mq.$small} {
+ .container > .attributes {
+ display: none;
+ }
+
+ header .info .attributes {
+ display: flex;
+ }
+ }
+}
+
+:host(:hover) {
+ border-color: var(--lumo-primary-color);
+}
+
+:host([hidden]) {
+ display: none;
+}
+
+:host(:first-child) {
+ margin-top: unset;
+}
+
+:host(:last-child) {
+ margin-bottom: unset;
+}
+
+:host([theme~="dark"]) {
+ background-color: var(--lumo-contrast);
+}
+
+:host {
+ [empty] {
+ user-select: none;
+ visibility: hidden;
+ }
+}
+
+:host([theme~="video"]) {
+ background-color: var(--video-background);
+}
+
+:host([theme~="training"]) {
+ background-color: var(--training-background);
+}
+
+:host([theme~="playbook"]) {
+ background-color: var(--playbook-background);
+}
+
+.container {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ gap: var(--lumo-space-s);
+ width: 100%;
+
+ > .attributes {
+ padding-top: 0;
+ }
+ }
+
+.attributes {
+ display: flex;
+ padding: var(--lumo-space-s) 0;
+ align-items: flex-start;
+ gap: var(--lumo-space-s);
+ align-self: stretch;
+ color: var(--lumo-shade-60pct);
+}
+
+header {
+ display: flex;
+ align-items: start;
+ justify-content: space-between;
+ gap: var(--lumo-space-m);
+
+ .info {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ gap: var(--lumo-space-xs);
+ flex: 1 0 0;
+ align-self: stretch;
+ max-width: calc(100% - var(--lumo-space-m) - 80px);
+ overflow: hidden;
+
+ .title {
+ color: #1A1A1A;
+ font-size: var(--lumo-font-size-xl);
+ font-family: Roboto;
+ font-style: normal;
+ font-weight: 700;
+ line-height: var(--lumo-line-height-xs);
+ }
+
+ .attributes {
+ display: none;
+ }
+ }
+
+
+ .instructor-image {
+ height: 92px;
+ width: 80px;
+
+ img {
+ height: 80px;
+ border-radius: 100px;
+ overflow: hidden;
+ }
+ }
+
+ .tags span {
+ &:first-child, &.new {
+ color: var(--lumo-primary-color)
+ }
+
+ &:first-child {
+ text-transform: capitalize;
+ }
+ }
+}
+
+.tags {
+ display: flex;
+ gap: var(--lumo-space-s);
+ max-width: 100%;
+ overflow: hidden;
+ flex-wrap: wrap;
+ min-height: 1.6em;
+
+ ::slotted(span):not(:first-child) {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+}
+
+.content {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ min-height: calc(1.2em * 8); // Eight standard line-heights
+
+ .tags {
+ ::slotted(span) {
+ font-style: italic;
+ }
+ }
+}
+
+footer {
+ position: relative;
+
+ vaadin-details[theme="reverse"] {
+ &::part(summary) {
+ justify-content: flex-start;
+ gap: var(--lumo-space-s);
+ font-size: var(--lumo-font-size-s);
+ }
+
+ &::part(toggle) {
+ padding: calc(var(--lumo-space-xs) / 4);
+ margin-left: initial;
+ font-size: var(--lumo-font-size-m);
+ transform: rotate(90deg);
+ }
+
+ &[opened]::part(toggle) {
+ transform: rotate(-90deg);
+ }
+
+ &::part(summary-content) {
+ color: var(--lumo-contrast);
+ font-weight: bold;
+ }
+
+ &::part(content) {
+ padding-bottom: 0;
+ font-size: var(--lumo-font-size-s);
+ }
+ }
+
+ vaadin-button.cta {
+ position: absolute;
+ top: 0;
+ right: 0;
+ font-weight: bold;
+
+ vaadin-icon {
+ background: var(--lumo-primary-color-10pct);
+ border-radius: 100%;
+ margin-left: var(--lumo-space-xs);
+ height: var(--lumo-icon-size-s);
+ width: var(--lumo-icon-size-s);
+ padding: calc(var(--lumo-space-xs) / 2);
+ }
+ }
+}
+
+vaadin-icon.badge-new {
+ display: none;
+}
+
+:host([new]) {
+ vaadin-icon.badge-new {
+ display: block;
+ position: absolute;
+ top: calc(-1 * var(--lumo-space-s));
+ right: calc(-1 * var(--lumo-space-s));
+ height: calc(2 * var(--lumo-space-m));
+ width: calc(2 * var(--lumo-space-m));
+ background: var(--lumo-primary-color);
+ padding: 6px;
+ color: var(--lumo-primary-contrast-color);
+ border-radius: 100%;
+ }
+}
diff --git a/packages/cxl-ui/src/components/cxl-course-card.js b/packages/cxl-ui/src/components/cxl-course-card.js
new file mode 100644
index 00000000..824778f8
--- /dev/null
+++ b/packages/cxl-ui/src/components/cxl-course-card.js
@@ -0,0 +1,102 @@
+/* eslint-disable import/no-extraneous-dependencies */
+import { LitElement, html } from 'lit';
+import { customElement, property, state } from 'lit/decorators.js';
+import '@vaadin/details';
+import '@vaadin/button';
+import cxlCourseCardStyles from '../styles/cxl-course-card-css.js';
+
+@customElement('cxl-course-card')
+export class CXLCourseCardElement extends LitElement {
+ static get styles() {
+ return [cxlCourseCardStyles];
+ }
+
+ separator = html` | `;
+
+ @state() _tagsHasChildren = false;
+
+ @state() _moreHasChildren = false;
+
+ @property({ type: String }) id = '';
+
+ @property({ type: String }) theme = 'course';
+
+ @property({ type: String }) title = '';
+
+ @property({ type: String }) time = '';
+
+ @property({ type: String }) instructor = '';
+
+ @property({ type: String }) avatar = '';
+
+ @property({ type: Boolean, reflect: true }) new = false;
+
+ @property({ type: String, attribute: 'cta-label' }) ctaLabel = 'View';
+
+ @property({ type: Boolean, attribute: 'cta-url' }) ctaUrl = false;
+
+ _slotHasChildren (e) {
+ const slot = e.target
+ const { name } = slot
+ const children = slot.assignedNodes()
+ this[`_${name}HasChildren`] = !!children.length
+ }
+
+ render() {
+ return html`
+
+
+
+
+ ${this.theme.toLowerCase() === 'course' ? html` ` : ''}
+ ${this.time}
+
+
+ By: ${this.instructor}
+
+
+
+
+
+ Read more
+
+
+ ${this.ctaLabel} ${this.theme}
+
+
+
+ `;
+ }
+}
diff --git a/packages/cxl-ui/src/index-core.js b/packages/cxl-ui/src/index-core.js
index 2ce06192..5dbb6ae7 100644
--- a/packages/cxl-ui/src/index-core.js
+++ b/packages/cxl-ui/src/index-core.js
@@ -15,6 +15,7 @@ import * as Headroom from 'headroom.js';
// CXL.
export { CXLAppLayoutElement } from './components/cxl-app-layout.js';
export { CXLCardElement } from './components/cxl-card.js';
+export { CXLCourseCardElement } from './components/cxl-course-card.js';
export { CXLCredentialElement } from './components/cxl-credential.js'
export { CXLCheckoutDetailsElement } from './components/cxl-checkout-details.js';
export { CXLMarketingNavElement } from './components/cxl-marketing-nav.js';
diff --git a/packages/cxl-ui/src/index-storybook.js b/packages/cxl-ui/src/index-storybook.js
index 2d17ec74..0e071ca3 100644
--- a/packages/cxl-ui/src/index-storybook.js
+++ b/packages/cxl-ui/src/index-storybook.js
@@ -4,6 +4,7 @@ import * as Headroom from 'headroom.js';
export { CXLAppLayoutElement } from './components/cxl-app-layout.js';
export { CXLCardElement } from './components/cxl-card.js';
+export { CXLCourseCardElement } from './components/cxl-course-card.js';
export { CXLMarketingNavElement } from './components/cxl-marketing-nav.js';
export { CXLSectionElement } from './components/cxl-section.js';
export { CXLStatsElement } from './components/cxl-stats.js';
diff --git a/packages/storybook/cxl-ui/cxl-course-card/[theme=course].stories.js b/packages/storybook/cxl-ui/cxl-course-card/[theme=course].stories.js
new file mode 100644
index 00000000..aa3edc1d
--- /dev/null
+++ b/packages/storybook/cxl-ui/cxl-course-card/[theme=course].stories.js
@@ -0,0 +1,15 @@
+import { CourseCardTemplate, args } from './template.js'
+import '@conversionxl/cxl-ui/src/components/cxl-course-card.js';
+import '@conversionxl/cxl-lumo-styles';
+
+export default {
+ title: 'CXL UI/cxl-course-card',
+ parameters: {
+ layout: 'centered'
+ }
+};
+
+
+export const CXLCourseCard = CourseCardTemplate.bind({});
+
+CXLCourseCard.args = args;
diff --git a/packages/storybook/cxl-ui/cxl-course-card/[theme=playbook].stories.js b/packages/storybook/cxl-ui/cxl-course-card/[theme=playbook].stories.js
new file mode 100644
index 00000000..c4b10235
--- /dev/null
+++ b/packages/storybook/cxl-ui/cxl-course-card/[theme=playbook].stories.js
@@ -0,0 +1,23 @@
+import { CourseCardTemplate, args } from './template.js'
+import '@conversionxl/cxl-ui/src/components/cxl-course-card.js';
+import '@conversionxl/cxl-lumo-styles';
+
+export default {
+ title: 'CXL UI/cxl-course-card',
+ parameters: {
+ layout: 'centered'
+ }
+};
+
+
+export const CXLCourseCardPlaybook = CourseCardTemplate.bind({});
+
+CXLCourseCardPlaybook.args = {
+ ...args,
+ theme: 'playbook',
+ title: 'The Why’s and How’s of Marketing Attribution',
+ time: '12 days ago',
+ description: 'Master the strategies, tactics, metrics, and wisdom you need to become an ABM leader and accelerate the growth of your company and of your career.',
+ contentTags: [],
+ tags: ['Growth Marketing']
+};
diff --git a/packages/storybook/cxl-ui/cxl-course-card/[theme=training].stories.js b/packages/storybook/cxl-ui/cxl-course-card/[theme=training].stories.js
new file mode 100644
index 00000000..e902ce48
--- /dev/null
+++ b/packages/storybook/cxl-ui/cxl-course-card/[theme=training].stories.js
@@ -0,0 +1,23 @@
+import { CourseCardTemplate, args } from './template.js'
+import '@conversionxl/cxl-ui/src/components/cxl-course-card.js';
+import '@conversionxl/cxl-lumo-styles';
+
+export default {
+ title: 'CXL UI/cxl-course-card',
+ parameters: {
+ layout: 'centered'
+ }
+};
+
+
+export const CXLCourseCardTraining = CourseCardTemplate.bind({});
+
+CXLCourseCardTraining.args = {
+ ...args,
+ theme: 'training',
+ title: 'A/B testing mastery',
+ description: 'Understand testing approaches that work (and pitfalls that don’t) to get more wins and insights from optimization efforts.',
+ tags: ['CRO', 'Branding'],
+ contentTags: [],
+ more: ''
+};
diff --git a/packages/storybook/cxl-ui/cxl-course-card/[theme=video].stories.js b/packages/storybook/cxl-ui/cxl-course-card/[theme=video].stories.js
new file mode 100644
index 00000000..74f9daa6
--- /dev/null
+++ b/packages/storybook/cxl-ui/cxl-course-card/[theme=video].stories.js
@@ -0,0 +1,24 @@
+import { CourseCardTemplate, args } from './template.js'
+import '@conversionxl/cxl-ui/src/components/cxl-course-card.js';
+import '@conversionxl/cxl-lumo-styles';
+
+export default {
+ title: 'CXL UI/cxl-course-card',
+ parameters: {
+ layout: 'centered'
+ }
+};
+
+
+export const CXLCourseCardVideo = CourseCardTemplate.bind({});
+
+CXLCourseCardVideo.args = {
+ ...args,
+ theme: 'video',
+ title: 'A/B testing mastery',
+ description: 'Understand testing approaches that work (and pitfalls that don’t) to get more wins and insights from optimization efforts.',
+ tags: ['CRO'],
+ contentTags: ['running experiments', 'customer base studies', 'prioritization' ],
+ time: '4 days ago',
+ new: true
+};
diff --git a/packages/storybook/cxl-ui/cxl-course-card/course-dashboard-varying-heights.stories.js b/packages/storybook/cxl-ui/cxl-course-card/course-dashboard-varying-heights.stories.js
new file mode 100644
index 00000000..980155f6
--- /dev/null
+++ b/packages/storybook/cxl-ui/cxl-course-card/course-dashboard-varying-heights.stories.js
@@ -0,0 +1,53 @@
+import { html } from 'lit';
+import '@conversionxl/cxl-ui/src/components/cxl-course-card.js';
+import '@conversionxl/cxl-lumo-styles';
+import { CXLCourseCard } from './[theme=course].stories.js';
+import { CXLCourseCardVideo } from './[theme=video].stories.js';
+import { CXLCourseCardPlaybook } from './[theme=playbook].stories.js';
+import { CXLCourseCardTraining } from './[theme=training].stories.js';
+
+export default {
+ title: 'CXL UI/cxl-course-card',
+};
+
+const ExtraCardVideoArgs = {
+ ...CXLCourseCardVideo.args,
+ contentTags: [...CXLCourseCardVideo.args.contentTags, 'Another tag', 'Much longer content tag' ],
+ description: `${CXLCourseCardVideo.args.description} One more line to test the layout behavior on longer descriptions, with at least four line of text.`
+}
+
+const ExtraCardTrainingArgs = {
+ ...CXLCourseCardTraining.args,
+ description: 'Short description',
+}
+
+const Template = () => html`
+
+
+
+ ${CXLCourseCard(CXLCourseCard.args)}
+ ${CXLCourseCardPlaybook(CXLCourseCardPlaybook.args)}
+ ${CXLCourseCardTraining(ExtraCardTrainingArgs)}
+ ${CXLCourseCardVideo(ExtraCardVideoArgs)}
+ ${CXLCourseCardVideo(CXLCourseCardVideo.args)}
+ ${CXLCourseCardTraining(CXLCourseCardTraining.args)}
+
+`;
+
+export const CoursesDashboardDifferentHeights = Template.bind({});
diff --git a/packages/storybook/cxl-ui/cxl-course-card/course-dashboard.stories.js b/packages/storybook/cxl-ui/cxl-course-card/course-dashboard.stories.js
new file mode 100644
index 00000000..c50fc163
--- /dev/null
+++ b/packages/storybook/cxl-ui/cxl-course-card/course-dashboard.stories.js
@@ -0,0 +1,40 @@
+import { html } from 'lit';
+import '@conversionxl/cxl-ui/src/components/cxl-course-card.js';
+import '@conversionxl/cxl-lumo-styles';
+import { CXLCourseCard } from './[theme=course].stories.js';
+import { CXLCourseCardVideo } from './[theme=video].stories.js';
+import { CXLCourseCardPlaybook } from './[theme=playbook].stories.js';
+import { CXLCourseCardTraining } from './[theme=training].stories.js';
+
+export default {
+ title: 'CXL UI/cxl-course-card',
+};
+
+const Template = () => html`
+
+
+
+ ${CXLCourseCard(CXLCourseCard.args)}
+ ${CXLCourseCardPlaybook(CXLCourseCardPlaybook.args)}
+ ${CXLCourseCardTraining(CXLCourseCardTraining.args)}
+ ${CXLCourseCardVideo(CXLCourseCardVideo.args)}
+
+`;
+
+export const CoursesDashboard = Template.bind({});
diff --git a/packages/storybook/cxl-ui/cxl-course-card/template.js b/packages/storybook/cxl-ui/cxl-course-card/template.js
new file mode 100644
index 00000000..7c87fc5a
--- /dev/null
+++ b/packages/storybook/cxl-ui/cxl-course-card/template.js
@@ -0,0 +1,35 @@
+import { html } from 'lit';
+
+const renderTags = (tags, slot) => tags.map((tag, i) => html`${i > 0 ? html` | ` : ''}${tag} `);
+
+export const CourseCardTemplate = (course) => html`
+
+ ${course.tags ? renderTags(course.tags, 'tags') : ''}
+ ${course.description}
+ ${course.contentTags ? renderTags(course.contentTags, 'content-tags') : ''}
+ ${course.more ? html` ${course.more}
` : ''}
+
+`;
+
+export const args = {
+ id: 'cxl-course-1',
+ title: 'Account based marketing',
+ time: '3h 00min',
+ instructor: 'Tom Wesseling',
+ description: 'Master the strategies, tactics, metrics, and wisdom you need to become an ABM leader and accelerate the growth of your company and of your career.',
+ contentTags: ['B2B', 'campaigns', 'pilot planning'],
+ theme: 'course',
+ tags: ['Marketing', 'Analytics'],
+ avatar: 'https://cxl.com/institute/wp-content/uploads/2020/05/48192546_10156982340630746_8127333122065825792_n-wpv_400pxx400px_center_center.jpg',
+ more: "Lorem ipsum dolor sit amet consectetur adipisicing elit.",
+ new: false
+}