diff --git a/docs/_sidebar.md b/docs/_sidebar.md
index d55eecb4..86dd8f06 100644
--- a/docs/_sidebar.md
+++ b/docs/_sidebar.md
@@ -12,6 +12,7 @@
- [Avatar](/components/avatar.md)
- [Badge](/components/badge.md)
- [Button](/components/button.md)
+ - [Card](/components/card.md)
- [Checkbox](/components/checkbox.md)
- [Color Picker](/components/color-picker.md)
- [Details](/components/details.md)
diff --git a/docs/components/card.md b/docs/components/card.md
new file mode 100644
index 00000000..3c4278d1
--- /dev/null
+++ b/docs/components/card.md
@@ -0,0 +1,151 @@
+# Card
+
+[component-header:sl-card]
+
+Cards can be used to group related subjects in a container.
+
+```html preview
+
+
+
+ Mittens
+ This kitten is as cute as he is playful. Bring him home today!
+ 6 weeks old
+
+
+ More Info
+
+
+
+
+
+```
+
+## Examples
+
+## Basic Card
+
+Basic cards aren't very exciting, but they can display any content you want them to.
+
+```html preview
+
+ This is just a basic card. No image, no header, and no footer. Just your content.
+
+
+
+```
+
+## Card with Header
+
+Headers can be used to display titles and other card options.
+
+```html preview
+
+
+
+```
+
+## Card with Footer
+
+Footers can be used to display actions, summaries, or other relevant content.
+
+```html preview
+
+
+
+```
+
+## Images
+
+Cards accept an `image` slot. The image is displayed atop the card and stretches to fit.
+
+```html preview
+
+
+ This is a kitten, but not just any kitten. This kitten likes walking along pallets.
+
+
+
+```
+
+[component-metadata:sl-card]
diff --git a/src/components.d.ts b/src/components.d.ts
index 0682cb36..b75392e8 100644
--- a/src/components.d.ts
+++ b/src/components.d.ts
@@ -106,6 +106,8 @@ export namespace Components {
*/
"value": string;
}
+ interface SlCard {
+ }
interface SlCheckbox {
/**
* Set to true to draw the checkbox in a checked state.
@@ -904,6 +906,12 @@ declare global {
prototype: HTMLSlButtonElement;
new (): HTMLSlButtonElement;
};
+ interface HTMLSlCardElement extends Components.SlCard, HTMLStencilElement {
+ }
+ var HTMLSlCardElement: {
+ prototype: HTMLSlCardElement;
+ new (): HTMLSlCardElement;
+ };
interface HTMLSlCheckboxElement extends Components.SlCheckbox, HTMLStencilElement {
}
var HTMLSlCheckboxElement: {
@@ -1071,6 +1079,7 @@ declare global {
"sl-avatar": HTMLSlAvatarElement;
"sl-badge": HTMLSlBadgeElement;
"sl-button": HTMLSlButtonElement;
+ "sl-card": HTMLSlCardElement;
"sl-checkbox": HTMLSlCheckboxElement;
"sl-color-picker": HTMLSlColorPickerElement;
"sl-details": HTMLSlDetailsElement;
@@ -1209,6 +1218,8 @@ declare namespace LocalJSX {
*/
"value"?: string;
}
+ interface SlCard {
+ }
interface SlCheckbox {
/**
* Set to true to draw the checkbox in a checked state.
@@ -2074,6 +2085,7 @@ declare namespace LocalJSX {
"sl-avatar": SlAvatar;
"sl-badge": SlBadge;
"sl-button": SlButton;
+ "sl-card": SlCard;
"sl-checkbox": SlCheckbox;
"sl-color-picker": SlColorPicker;
"sl-details": SlDetails;
@@ -2111,6 +2123,7 @@ declare module "@stencil/core" {
"sl-avatar": LocalJSX.SlAvatar & JSXBase.HTMLAttributes;
"sl-badge": LocalJSX.SlBadge & JSXBase.HTMLAttributes;
"sl-button": LocalJSX.SlButton & JSXBase.HTMLAttributes;
+ "sl-card": LocalJSX.SlCard & JSXBase.HTMLAttributes;
"sl-checkbox": LocalJSX.SlCheckbox & JSXBase.HTMLAttributes;
"sl-color-picker": LocalJSX.SlColorPicker & JSXBase.HTMLAttributes;
"sl-details": LocalJSX.SlDetails & JSXBase.HTMLAttributes;
diff --git a/src/components/card/card.scss b/src/components/card/card.scss
new file mode 100644
index 00000000..45b8eb5f
--- /dev/null
+++ b/src/components/card/card.scss
@@ -0,0 +1,65 @@
+@import 'component';
+
+/**
+ * @prop --border-color: The card's border color, including borders that occur inside the card.
+ * @prop --border-radius: The border radius for card edges.
+ * @prop --border-width: The width of card borders.
+ * @prop --padding: The padding to use for card sections.
+ */
+:host {
+ --border-color: var(--sl-color-gray-90);
+ --border-radius: var(--sl-border-radius-medium);
+ --border-width: 1px;
+ --padding: var(--sl-spacing-large);
+
+ display: inline-block;
+}
+
+.card {
+ display: flex;
+ flex-direction: column;
+ background-color: var(--sl-color-white);
+ box-shadow: var(--sl-shadow-x-small);
+ border: solid var(--border-width) var(--border-color);
+ border-radius: var(--border-radius);
+}
+
+.card__image {
+ border-top-left-radius: calc(var(--border-radius) - var(--border-width));
+ border-top-right-radius: calc(var(--border-radius) - var(--border-width));
+ border-top-left-radius: var(--border-radius);
+ border-top-right-radius: var(--border-radius);
+ margin: calc(-1 * var(--border-width));
+ overflow: hidden;
+
+ ::slotted(img) {
+ display: block;
+ width: 100%;
+ }
+}
+
+.card:not(.card--has-image) .card__image {
+ display: none;
+}
+
+.card__header {
+ border-bottom: solid var(--border-width) var(--border-color);
+ padding: calc(var(--padding) / 2) var(--padding);
+}
+
+.card:not(.card--has-header) .card__header {
+ display: none;
+}
+
+.card__body {
+ padding: var(--padding);
+}
+
+.card--has-footer .card__footer {
+ border-top: solid var(--border-width) var(--border-color);
+ padding: var(--padding);
+}
+
+.card:not(.card--has-footer) .card__footer {
+ display: none;
+}
diff --git a/src/components/card/card.tsx b/src/components/card/card.tsx
new file mode 100644
index 00000000..ecdd88f0
--- /dev/null
+++ b/src/components/card/card.tsx
@@ -0,0 +1,76 @@
+import { Component, Element, State, h } from '@stencil/core';
+import { hasSlot } from '../../utilities/slot';
+
+/**
+ * @since 2.0
+ * @status stable
+ *
+ * @slot - The card's body.
+ * @slot header - The card's header.
+ * @slot footer - The card's footer.
+ * @slot image - The card's image.
+ *
+ * @part base - The component's base wrapper.
+ * @part image - The card's image, if present.
+ * @part header - The card's header, if present.
+ * @part body - The card's body.
+ * @part footer - The card's footer, if present.
+ */
+
+@Component({
+ tag: 'sl-card',
+ styleUrl: 'card.scss',
+ shadow: true
+})
+export class Card {
+ @Element() host: HTMLSlCardElement;
+
+ @State() hasFooter = false;
+ @State() hasImage = false;
+ @State() hasHeader = false;
+
+ componentWillLoad() {
+ this.updateSlots();
+ this.host.shadowRoot.addEventListener('slotchange', this.updateSlots);
+ }
+
+ componentDidUnload() {
+ this.host.shadowRoot.removeEventListener('slotchange', this.updateSlots);
+ }
+
+ updateSlots() {
+ this.hasFooter = hasSlot(this.host, 'footer');
+ this.hasImage = hasSlot(this.host, 'image');
+ this.hasHeader = hasSlot(this.host, 'header');
+ }
+
+ render() {
+ return (
+
+ );
+ }
+}