diff --git a/.changeset/icy-buttons-lay.md b/.changeset/icy-buttons-lay.md
new file mode 100644
index 0000000000..2dc6285bb7
--- /dev/null
+++ b/.changeset/icy-buttons-lay.md
@@ -0,0 +1,7 @@
+---
+'@sl-design-system/panel': minor
+---
+
+From now on, use `default` and `relaxed` for the `density` property.
+The `plain` and `comfortable` values are deprecated, they will be kept for the backward compatibility for now but removed in the future.
+Please use `default` or `relaxed` from now on.
diff --git a/.changeset/some-rabbits-wonder.md b/.changeset/some-rabbits-wonder.md
new file mode 100644
index 0000000000..0faa791ad7
--- /dev/null
+++ b/.changeset/some-rabbits-wonder.md
@@ -0,0 +1,8 @@
+---
+'@sl-design-system/callout': patch
+---
+
+New `callout` component, visually similar to the `inline-message` but with a different purpose:
+it can be used to provide additional, non-interrupting information and may include actions (e.g. buttons).
+It must not be shown/hidden dynamically in response to user actions (unlike the `inline-message`).
+There is no ARIA role on this component as it is not meant to interrupt the user (no live region).
\ No newline at end of file
diff --git a/chromatic/.storybook/stories/all.stories.ts b/chromatic/.storybook/stories/all.stories.ts
index d7666e0c8c..b54ada115e 100644
--- a/chromatic/.storybook/stories/all.stories.ts
+++ b/chromatic/.storybook/stories/all.stories.ts
@@ -4,6 +4,7 @@ import { Colors, Sizes } from '../../../packages/components/avatar/src/avatar.st
import { All as AllBadge } from '../../../packages/components/badge/src/badge.stories';
import { All as AllBreadcrumbs } from '../../../packages/components/breadcrumbs/src/breadcrumbs.stories';
import { All as AllButton } from '../../../packages/components/button/src/button.stories';
+import { All as AllCallout } from '../../../packages/components/callout/src/callout.stories';
import { All as AllCard } from '../../../packages/components/card/src/card.stories';
import { All as AllCheckbox } from '../../../packages/components/checkbox/src/root.stories';
import { All as AllCombobox } from '../../../packages/components/combobox/src/combobox.stories';
@@ -69,6 +70,7 @@ export const AvatarSizes = { render: Sizes.render };
export const Badge = { render: AllBadge.render };
export const Breadcrumbs = { render: AllBreadcrumbs.render };
export const Button = { render: AllButton.render };
+export const Callout = { render: AllCallout.render };
export const Card = { render: AllCard.render };
export const Checkbox = { render: AllCheckbox.render };
export const Combobox = { render: AllCombobox.render };
diff --git a/packages/components/callout/index.ts b/packages/components/callout/index.ts
new file mode 100644
index 0000000000..ed80defc4b
--- /dev/null
+++ b/packages/components/callout/index.ts
@@ -0,0 +1 @@
+export * from './src/callout.js';
diff --git a/packages/components/callout/package.json b/packages/components/callout/package.json
new file mode 100644
index 0000000000..54c2845adc
--- /dev/null
+++ b/packages/components/callout/package.json
@@ -0,0 +1,52 @@
+{
+ "name": "@sl-design-system/callout",
+ "version": "0.0.1",
+ "description": "Callout component for the SL Design System",
+ "license": "Apache-2.0",
+ "publishConfig": {
+ "registry": "https://npm.pkg.github.com"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/sl-design-system/components.git",
+ "directory": "packages/components/callout"
+ },
+ "homepage": "https://sanomalearning.design/components/callout",
+ "bugs": {
+ "url": "https://github.com/sl-design-system/components/issues"
+ },
+ "type": "module",
+ "main": "./index.js",
+ "module": "./index.js",
+ "types": "./index.d.ts",
+ "customElements": "custom-elements.json",
+ "exports": {
+ ".": "./index.js",
+ "./package.json": "./package.json",
+ "./register.js": "./register.js"
+ },
+ "files": [
+ "**/*.d.ts",
+ "**/*.js",
+ "**/*.js.map",
+ "custom-elements.json"
+ ],
+ "sideEffects": [
+ "register.js"
+ ],
+ "scripts": {
+ "test": "echo \"Error: run tests from monorepo root.\" && exit 1"
+ },
+ "dependencies": {
+ "@sl-design-system/icon": "^1.3.0",
+ "@sl-design-system/shared": "^0.9.0"
+ },
+ "devDependencies": {
+ "@lit/localize": "^0.12.2",
+ "@open-wc/scoped-elements": "^3.0.6"
+ },
+ "peerDependencies": {
+ "@lit/localize": "^0.12.1",
+ "@open-wc/scoped-elements": "^3.0.6"
+ }
+}
diff --git a/packages/components/callout/register.ts b/packages/components/callout/register.ts
new file mode 100644
index 0000000000..5d8df22fb7
--- /dev/null
+++ b/packages/components/callout/register.ts
@@ -0,0 +1,3 @@
+import { Callout } from './src/callout.js';
+
+customElements.define('sl-callout', Callout);
diff --git a/packages/components/callout/src/callout.scss b/packages/components/callout/src/callout.scss
new file mode 100644
index 0000000000..803fd97baa
--- /dev/null
+++ b/packages/components/callout/src/callout.scss
@@ -0,0 +1,71 @@
+:host {
+ align-items: center;
+ background: var(--sl-color-background-info-subtlest);
+ border-radius: var(--sl-size-borderRadius-default);
+ color: var(--sl-color-foreground-info-bold);
+ display: grid;
+ gap: 0 var(--sl-size-100);
+ grid-template-columns: auto 1fr;
+ padding: var(--sl-size-150) var(--sl-size-200);
+}
+
+:host([density='relaxed']) {
+ padding: var(--sl-size-300);
+}
+
+:host([no-title]) {
+ [part='title'] {
+ display: none;
+ }
+
+ [part='content'] {
+ grid-area: 1 / 2;
+ }
+}
+
+:host([variant='positive']) {
+ background: var(--sl-color-background-positive-subtlest);
+ color: var(--sl-color-foreground-positive-bold);
+}
+
+:host([variant='negative']) {
+ background: var(--sl-color-background-negative-subtlest);
+ color: var(--sl-color-foreground-negative-bold);
+}
+
+:host([variant='caution']) {
+ background: var(--sl-color-background-caution-subtlest);
+ color: var(--sl-color-foreground-caution-bold);
+}
+
+[part='content'] {
+ grid-column: 2;
+
+ slot {
+ display: block;
+ text-wrap: pretty;
+ }
+}
+
+[part='content'] > * {
+ display: block;
+}
+
+[part='icon'] {
+ align-items: center;
+ align-self: start;
+ block-size: 1lh;
+ display: inline-flex;
+}
+
+[part='title'] {
+ align-self: start;
+ font-size: calc((16 / 14) * 1em);
+ font-weight: var(--sl-text-new-typeset-fontWeight-semiBold);
+ grid-area: 1 / 2;
+ margin-block-end: var(--sl-size-025);
+
+ slot {
+ display: block;
+ }
+}
diff --git a/packages/components/callout/src/callout.spec.ts b/packages/components/callout/src/callout.spec.ts
new file mode 100644
index 0000000000..231eb2789b
--- /dev/null
+++ b/packages/components/callout/src/callout.spec.ts
@@ -0,0 +1,49 @@
+import { fixture } from '@sl-design-system/vitest-browser-lit';
+import { html } from 'lit';
+import { beforeEach, describe, expect, it } from 'vitest';
+import '../register.js';
+import { Callout } from './callout.js';
+
+describe('sl-callout', () => {
+ let el: Callout;
+
+ describe('defaults', () => {
+ beforeEach(async () => {
+ el = await fixture(html`Callout component`);
+ });
+
+ it('should not have an explicit density', () => {
+ expect(el).not.to.have.attribute('density');
+ expect(el.density).to.be.undefined;
+ });
+
+ it('should not have an explicit variant', () => {
+ expect(el).not.to.have.attribute('variant');
+ expect(el.variant).to.be.undefined;
+ });
+
+ it('should have positive variant when set', async () => {
+ el.variant = 'positive';
+ await el.updateComplete;
+
+ expect(el).to.have.attribute('variant', 'positive');
+ });
+ });
+
+ describe('no title', () => {
+ beforeEach(async () => {
+ el = await fixture(html`Callout component text`);
+ });
+
+ it('should not display a title', () => {
+ const title = el.renderRoot.querySelector('[part="title"]')!;
+
+ expect(title).to.exist;
+ expect(title).to.have.style('display', 'none');
+ });
+
+ it('should have the no-title attribute set', () => {
+ expect(el).to.have.attribute('no-title');
+ });
+ });
+});
diff --git a/packages/components/callout/src/callout.stories.ts b/packages/components/callout/src/callout.stories.ts
new file mode 100644
index 0000000000..a74ab9548d
--- /dev/null
+++ b/packages/components/callout/src/callout.stories.ts
@@ -0,0 +1,267 @@
+import { faArrowDownToLine, faArrowRightToBracket } from '@fortawesome/pro-regular-svg-icons';
+import { faFileSignature as fasFileSignature, faShield as fasShield } from '@fortawesome/pro-solid-svg-icons';
+import '@sl-design-system/button/register.js';
+import { Icon } from '@sl-design-system/icon';
+import '@sl-design-system/icon/register.js';
+import { type Meta, type StoryObj } from '@storybook/web-components-vite';
+import { type TemplateResult, html, nothing } from 'lit';
+import { ifDefined } from 'lit/directives/if-defined.js';
+import '../register.js';
+import { Callout, type CalloutVariant } from './callout.js';
+
+interface Props extends Pick {
+ title: string;
+ button: string;
+ body: string | (() => TemplateResult);
+}
+type Story = StoryObj;
+
+const variants: CalloutVariant[] = ['info', 'positive', 'caution', 'negative'];
+
+Icon.register(faArrowDownToLine, faArrowRightToBracket, fasFileSignature, fasShield);
+
+export default {
+ title: 'Feedback & status/Callout',
+ tags: ['draft'],
+ args: {
+ variant: 'info'
+ },
+ argTypes: {
+ body: {
+ table: {
+ disable: true
+ }
+ },
+ button: {
+ table: {
+ disable: true
+ }
+ },
+ density: {
+ control: 'inline-radio',
+ options: ['default', 'relaxed']
+ },
+ variant: {
+ control: 'inline-radio',
+ options: variants
+ }
+ },
+ render: ({ body, density, title, variant }) => html`
+
+
+ ${title ? html`
${title}
` : nothing} ${typeof body === 'string' ? body : body()}
+
+ `
+} satisfies Meta;
+
+export const Basic: Story = {
+ args: {
+ body: 'The main content of the callout'
+ }
+};
+
+export const Title: Story = {
+ args: {
+ title: 'Callout title',
+ body: () => html`The content of the callout.`
+ }
+};
+
+export const CustomIcon: Story = {
+ args: {
+ ...Basic.args,
+ body: () => html`
+
+ This example showcases how you can slot a different icon than the default one.
+ `
+ }
+};
+
+export const Overflow: Story = {
+ args: {
+ title:
+ 'Excepteur officia qui nisi commodo ullamco labore dolor ipsum eu non Lorem. Aute enim quis sit id laboris consequat nisi esse.',
+ body: 'Duis laborum consectetur mollit deserunt nostrud anim occaecat elit ipsum laborum. Ad sit in anim aliqua laborum tempor. Labore cupidatat aute magna consectetur ullamco occaecat ea nostrud velit exercitation nulla est anim.'
+ }
+};
+
+export const Density: Story = {
+ render: ({ variant }) => html`
+
+ Default callout component.
+
+
+ ${variants.map(
+ variant => html`
+ The main content of the callout
+
+
Callout title
+ The main content of the callout
+
+ The main content of the callout
+
+
+ The "${variant}" callout title, esse laboris nisi ut quis ullamco dolor elit do commodo ea mollit eu
+ irure.
+
+ Duis ut magna commodo minim cillum voluptate incididunt ea labore adipisicing do ad anim. Incididunt non
+ consequat eiusmod aliqua consequat Lorem eu culpa labore aute laboris eiusmod.
+
+ `
+ )}
+
+ `
+};
diff --git a/packages/components/callout/src/callout.ts b/packages/components/callout/src/callout.ts
new file mode 100644
index 0000000000..27c077c576
--- /dev/null
+++ b/packages/components/callout/src/callout.ts
@@ -0,0 +1,93 @@
+import { localized } from '@lit/localize';
+import { type ScopedElementsMap, ScopedElementsMixin } from '@open-wc/scoped-elements/lit-element.js';
+import { Icon } from '@sl-design-system/icon';
+import { type CSSResultGroup, LitElement, type TemplateResult, html } from 'lit';
+import { property } from 'lit/decorators.js';
+import styles from './callout.scss.js';
+
+declare global {
+ interface HTMLElementTagNameMap {
+ 'sl-callout': Callout;
+ }
+}
+
+export type CalloutDensity = 'default' | 'relaxed';
+
+export type CalloutVariant = 'info' | 'positive' | 'caution' | 'negative';
+
+/**
+ * A callout component for displaying additional information.
+ * The component can contain actions (e.g. buttons)
+ * and should not be shown/hidden dynamically in response to user actions
+ * (unlike the inline-message).
+ * This means the callout should remain visible as part of the static page layout,
+ * rather than appearing or disappearing based on user interaction.
+ * There is no aria role on this component as it is not meant to interrupt the user.
+ *
+ * @slot default - The body of the callout.
+ * @slot icon - Icon shown on the left side of the component.
+ * @slot title - Title content for the callout.
+ */
+@localized()
+export class Callout extends ScopedElementsMixin(LitElement) {
+ /** @internal */
+ static get scopedElements(): ScopedElementsMap {
+ return {
+ 'sl-icon': Icon
+ };
+ }
+
+ /** @internal */
+ static override styles: CSSResultGroup = styles;
+
+ /** @internal The name of the icon, depending on the variant. */
+ get iconName(): string {
+ switch (this.variant) {
+ case 'positive':
+ return 'circle-check-solid';
+ case 'caution':
+ return 'octagon-exclamation-solid';
+ case 'negative':
+ return 'diamond-exclamation-solid';
+ default:
+ return 'info';
+ }
+ }
+
+ /** @internal If the title is missing, the content needs to be placed where the title should be. */
+ @property({ type: Boolean, attribute: 'no-title', reflect: true }) noTitle = true;
+
+ /**
+ * The density of the callout.
+ * @default 'default'
+ */
+ @property({ reflect: true }) density?: CalloutDensity;
+
+ /**
+ * The variant of the callout.
+ * @default 'info'
+ */
+ @property({ reflect: true }) variant?: CalloutVariant;
+
+ override render(): TemplateResult {
+ return html`
+
Density adjusts the internal spacing of content within the panel.
- The default spacing for general use.
- Adds extra padding for a more relaxed layout.
+ The default spacing for general use.
+ Adds extra padding for a more relaxed layout.
Divider
Dividers can be added to visually separate sections within a panel.
diff --git a/packages/components/panel/src/panel.ts b/packages/components/panel/src/panel.ts
index 78aa27838e..b036481217 100644
--- a/packages/components/panel/src/panel.ts
+++ b/packages/components/panel/src/panel.ts
@@ -16,7 +16,7 @@ declare global {
}
}
-export type PanelDensity = 'plain' | 'comfortable';
+export type PanelDensity = 'default' | 'relaxed' | 'plain' | 'comfortable'; // 'plain' and 'comfortable' are deprecated, should be removed in the future
export type PanelElevation = 'none' | 'raised' | 'sunken';
@@ -63,7 +63,9 @@ export class Panel extends ScopedElementsMixin(LitElement) {
/**
* The density of the panel.
- * @default plain
+ * Note: the `plain` and `comfortable` density values are deprecated and will be removed in the future.
+ * @param {'plain' | 'comfortable'} - These density values are deprecated and will be removed in the future.
+ * @default 'default'
*/
@property({ reflect: true }) density?: PanelDensity;
diff --git a/tsconfig.all.json b/tsconfig.all.json
index c5ea2ff4d1..67436b4c1b 100644
--- a/tsconfig.all.json
+++ b/tsconfig.all.json
@@ -25,6 +25,7 @@
{ "path": "./packages/components/button" },
{ "path": "./packages/components/button-bar" },
{ "path": "./packages/components/calendar" },
+ { "path": "./packages/components/callout" },
{ "path": "./packages/components/card" },
{ "path": "./packages/components/checkbox" },
{ "path": "./packages/components/combobox" },
diff --git a/website/src/categories/components/panel/panel.json b/website/src/categories/components/panel/panel.json
index 8666e663ed..c6c03a95f8 100644
--- a/website/src/categories/components/panel/panel.json
+++ b/website/src/categories/components/panel/panel.json
@@ -1,7 +1,7 @@
{
"layout": "categories/components/components.njk",
"tags": "panel",
- "componentStatus": "preview",
+ "componentStatus": "draft",
"componentTagName": [
{
"name": "Panel",
diff --git a/yarn.lock b/yarn.lock
index 0d0a2d3828..bb034d5953 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4628,6 +4628,20 @@ __metadata:
languageName: unknown
linkType: soft
+"@sl-design-system/callout@workspace:packages/components/callout":
+ version: 0.0.0-use.local
+ resolution: "@sl-design-system/callout@workspace:packages/components/callout"
+ dependencies:
+ "@lit/localize": "npm:^0.12.2"
+ "@open-wc/scoped-elements": "npm:^3.0.6"
+ "@sl-design-system/icon": "npm:^1.3.0"
+ "@sl-design-system/shared": "npm:^0.9.0"
+ peerDependencies:
+ "@lit/localize": ^0.12.1
+ "@open-wc/scoped-elements": ^3.0.6
+ languageName: unknown
+ linkType: soft
+
"@sl-design-system/card@workspace:packages/components/card":
version: 0.0.0-use.local
resolution: "@sl-design-system/card@workspace:packages/components/card"