diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/add-styles/index.html b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/add-styles/index.html
new file mode 100644
index 000000000..57a0c9aca
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/add-styles/index.html
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/add-styles/my-element.ts b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/add-styles/my-element.ts
new file mode 100644
index 000000000..9fc3fecd0
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/add-styles/my-element.ts
@@ -0,0 +1,18 @@
+import { html, LitElement, css } from 'lit';
+import { customElement } from 'lit/decorators.js';
+
+@customElement('my-element')
+export class MyElement extends LitElement {
+ render() {
+ return html`
I'm blue
I'm red
`;
+ }
+
+ static styles = css`
+ p {
+ color: blue;
+ }
+ div {
+ color: red;
+ }
+ `;
+}
\ No newline at end of file
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/add-styles/project.json b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/add-styles/project.json
new file mode 100644
index 000000000..816404c3d
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/add-styles/project.json
@@ -0,0 +1,8 @@
+{
+ "extends": "/samples/v3-base.json",
+ "files": {
+ "my-element.ts": {},
+ "index.html": {}
+ },
+ "previewHeight": "100px"
+}
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/bind-tag-name/index.html b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/bind-tag-name/index.html
new file mode 100644
index 000000000..57a0c9aca
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/bind-tag-name/index.html
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/bind-tag-name/input-or-textfield.ts b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/bind-tag-name/input-or-textfield.ts
new file mode 100644
index 000000000..d85b50100
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/bind-tag-name/input-or-textfield.ts
@@ -0,0 +1,35 @@
+import { html, LitElement } from 'lit';
+import { customElement, property } from 'lit/decorators.js';
+import { html as staticHTML, StaticValue } from 'lit/static-html.js';
+import { live } from 'lit/directives/live.js';
+
+@customElement('input-or-textfield')
+export class MyElement extends LitElement {
+ // attribute is false because this is a value that can't be serialized to an
+ // HTML attribute
+ @property({ attribute: false }) tagLiteral: StaticValue|null = null;
+ @property() value = '';
+
+ render() {
+ return html`
+ ${
+ // NOTE: the live() directive prevents setting the .value property if
+ // the live value of the input / textfield already matches this.value.
+ // This is important since static html templates should not be thrashed
+ // due to performance concerns.
+ staticHTML`
+ <${this.tagLiteral}
+ @input=${this.#onInput}
+ .value=${live(this.value)}>${this.tagLiteral}>
+ `
+ }
+
`;
+ }
+
+ static styles = css`
+ .red {
+ color: red;
+ }
+ .blue {
+ color: blue;
+ }
+ `;
+}
\ No newline at end of file
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/classes/project.json b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/classes/project.json
new file mode 100644
index 000000000..816404c3d
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/classes/project.json
@@ -0,0 +1,8 @@
+{
+ "extends": "/samples/v3-base.json",
+ "files": {
+ "my-element.ts": {},
+ "index.html": {}
+ },
+ "previewHeight": "100px"
+}
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/conditionals/index.html b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/conditionals/index.html
new file mode 100644
index 000000000..57a0c9aca
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/conditionals/index.html
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/conditionals/my-element.ts b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/conditionals/my-element.ts
new file mode 100644
index 000000000..860091f41
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/conditionals/my-element.ts
@@ -0,0 +1,26 @@
+import { html, LitElement } from 'lit';
+import { customElement, state } from 'lit/decorators.js';
+
+@customElement('my-element')
+export class MyElement extends LitElement {
+ @state() private someBoolean = false;
+
+ render() {
+ let someText = html`
Some text
`;
+
+ if (this.someBoolean) {
+ someText = html`
Some other text
`;
+ }
+
+ return html`
+
+
This is an inline ternary conditional
+ ${this.someBoolean ? html`
Some other text
` : html`
Some text
`}
+
This is a variable conditional
+ ${someText}
+ `;
+ }
+}
\ No newline at end of file
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/conditionals/project.json b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/conditionals/project.json
new file mode 100644
index 000000000..9c791c2a3
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/conditionals/project.json
@@ -0,0 +1,8 @@
+{
+ "extends": "/samples/v3-base.json",
+ "files": {
+ "my-element.ts": {},
+ "index.html": {}
+ },
+ "previewHeight": "200px"
+}
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/constructable-stylesheets/index.html b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/constructable-stylesheets/index.html
new file mode 100644
index 000000000..57a0c9aca
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/constructable-stylesheets/index.html
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/constructable-stylesheets/my-element.ts b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/constructable-stylesheets/my-element.ts
new file mode 100644
index 000000000..8bd747eee
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/constructable-stylesheets/my-element.ts
@@ -0,0 +1,20 @@
+import { html, LitElement } from 'lit';
+import { customElement } from 'lit/decorators.js';
+import { trustedStyles, type CSSStyleSheet } from './trusted-stringified-css-source.js';
+
+// Use constructable stylesheets on TRUSTED CSS strings to use them in a LitElement
+const styles = new CSSStyleSheet();
+// this type assertion is needed for the older version of TS like that the lit.dev website uses
+(styles as unknown as CSSStyleSheet).replace(trustedStyles);
+
+@customElement('my-element')
+export class MyElement extends LitElement {
+ static styles = styles;
+ render() {
+ return html`
+
+ This should be red!
+
+ `;
+ }
+}
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/constructable-stylesheets/project.json b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/constructable-stylesheets/project.json
new file mode 100644
index 000000000..e170474a0
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/constructable-stylesheets/project.json
@@ -0,0 +1,9 @@
+{
+ "extends": "/samples/v3-base.json",
+ "files": {
+ "my-element.ts": {},
+ "trusted-stringified-css-source.ts": {},
+ "index.html": {}
+ },
+ "previewHeight": "100px"
+}
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/constructable-stylesheets/trusted-stringified-css-source.ts b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/constructable-stylesheets/trusted-stringified-css-source.ts
new file mode 100644
index 000000000..fce4ea3be
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/constructable-stylesheets/trusted-stringified-css-source.ts
@@ -0,0 +1,11 @@
+export const trustedStyles = `
+ div {
+ color: red;
+ }
+`;
+
+// This may be needed for some older versions of TS
+export type CSSStyleSheet = typeof globalThis['CSSStyleSheet'] & {
+ replaceSync(cssText: string): void;
+ replace(cssText: string): void;
+};
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/css-shadow-parts/index.html b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/css-shadow-parts/index.html
new file mode 100644
index 000000000..63ef6c4b2
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/css-shadow-parts/index.html
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/css-shadow-parts/my-element.ts b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/css-shadow-parts/my-element.ts
new file mode 100644
index 000000000..6fac25d2d
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/css-shadow-parts/my-element.ts
@@ -0,0 +1,18 @@
+import { html, LitElement, css } from 'lit';
+import { customElement } from 'lit/decorators.js';
+
+@customElement('my-element')
+export class MyElement extends LitElement {
+ static styles = css`
+ p {
+ color: blue;
+ border: 1px solid black;
+ padding: 4px;
+ margin-block: 4px;
+ }
+ `;
+
+ render() {
+ return html`
This is in a shadow root!
`;
+ }
+}
\ No newline at end of file
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/css-shadow-parts/project.json b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/css-shadow-parts/project.json
new file mode 100644
index 000000000..875a71aaf
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/css-shadow-parts/project.json
@@ -0,0 +1,8 @@
+{
+ "extends": "/samples/v3-base.json",
+ "files": {
+ "my-element.ts": {},
+ "index.html": {}
+ },
+ "previewHeight": "120px"
+}
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/custom-attribute-converter/index.html b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/custom-attribute-converter/index.html
new file mode 100644
index 000000000..d0fdb30d1
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/custom-attribute-converter/index.html
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/custom-attribute-converter/my-element.ts b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/custom-attribute-converter/my-element.ts
new file mode 100644
index 000000000..40810f9c3
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/custom-attribute-converter/my-element.ts
@@ -0,0 +1,33 @@
+import { html, LitElement } from 'lit';
+import { customElement, property, state } from 'lit/decorators.js';import {ComplexAttributeConverter} from 'lit';
+
+/**
+ * Bidirectionally converts an array from an attribute to a property of the
+ * following format:
+ *
+ * array-attribute='1, "2", 3' to [1, '2', 3]
+ */
+export const arrayConverter: ComplexAttributeConverter> = {
+ toAttribute: (array: Array) => {
+ return JSON.stringify(array).substring(1, JSON.stringify(array).length - 1);
+ },
+ fromAttribute: (value: string) => {
+ try {
+ return JSON.parse(`[${value}]`);
+ } catch {
+ return [];
+ }
+ }
+};
+
+@customElement('my-element')
+export class MyElement extends LitElement {
+ @property({ converter: arrayConverter, reflect: true })
+ array: Array = [];
+
+ render() {
+ return this.array.map((item) =>
+ html`
${typeof item}: ${item}
`
+ );
+ }
+}
\ No newline at end of file
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/custom-attribute-converter/project.json b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/custom-attribute-converter/project.json
new file mode 100644
index 000000000..9c791c2a3
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/custom-attribute-converter/project.json
@@ -0,0 +1,8 @@
+{
+ "extends": "/samples/v3-base.json",
+ "files": {
+ "my-element.ts": {},
+ "index.html": {}
+ },
+ "previewHeight": "200px"
+}
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/data-up/game-player.ts b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/data-up/game-player.ts
new file mode 100644
index 000000000..fdfdab6a8
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/data-up/game-player.ts
@@ -0,0 +1,18 @@
+import { html, LitElement } from 'lit';
+import { customElement } from 'lit/decorators.js';
+
+export type ScoreEvent = CustomEvent;
+
+@customElement('game-player')
+export class GamePlayer extends LitElement {
+ render() {
+ return html`
+
+
+ `;
+ }
+
+ handleScore(points: number) {
+ this.dispatchEvent(new CustomEvent('score', { detail: points, bubbles: true }));
+ }
+}
\ No newline at end of file
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/data-up/index.html b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/data-up/index.html
new file mode 100644
index 000000000..0e004d712
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/data-up/index.html
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/data-up/project.json b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/data-up/project.json
new file mode 100644
index 000000000..9ccb50497
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/data-up/project.json
@@ -0,0 +1,9 @@
+{
+ "extends": "/samples/v3-base.json",
+ "files": {
+ "score-board.ts": {},
+ "game-player.ts": {},
+ "index.html": {}
+ },
+ "previewHeight": "250px"
+}
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/data-up/score-board.ts b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/data-up/score-board.ts
new file mode 100644
index 000000000..6a8d3dd36
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/data-up/score-board.ts
@@ -0,0 +1,20 @@
+import { html, LitElement } from 'lit';
+import { customElement, state } from 'lit/decorators.js';
+import './game-player.js';
+import type {ScoreEvent} from './game-player.js';
+
+@customElement('score-board')
+export class ScoreBoard extends LitElement {
+ @state() playerOneScore = 0;
+ @state() playerTwoScore = 0;
+
+ render() {
+ return html`
+
${this.playerOneScore} - ${this.playerTwoScore}
+
Player 1
+ this.playerOneScore += e.detail}>
+
Player 2
+ this.playerTwoScore += e.detail}>
+ `;
+ }
+}
\ No newline at end of file
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/define/hello-world.ts b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/define/hello-world.ts
new file mode 100644
index 000000000..b4f5ba4d5
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/define/hello-world.ts
@@ -0,0 +1,9 @@
+import { html, LitElement } from 'lit';
+import { customElement } from 'lit/decorators.js';
+
+@customElement('hello-world')
+export class HelloWorld extends LitElement {
+ render() {
+ return html`
Hello, world!
`;
+ }
+}
\ No newline at end of file
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/define/index.html b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/define/index.html
new file mode 100644
index 000000000..c53974764
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/define/index.html
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/define/project.json b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/define/project.json
new file mode 100644
index 000000000..6430bb8e9
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/define/project.json
@@ -0,0 +1,8 @@
+{
+ "extends": "/samples/v3-base.json",
+ "files": {
+ "hello-world.ts": {},
+ "index.html": {}
+ },
+ "previewHeight": "100px"
+}
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/dom-qae/index.html b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/dom-qae/index.html
new file mode 100644
index 000000000..e44dfda1c
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/dom-qae/index.html
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/dom-qae/my-pretty-input.ts b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/dom-qae/my-pretty-input.ts
new file mode 100644
index 000000000..26ba98f12
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/dom-qae/my-pretty-input.ts
@@ -0,0 +1,85 @@
+import { html, LitElement, css } from 'lit';
+import { customElement, property, queryAssignedElements, state } from 'lit/decorators.js';
+
+@customElement('my-pretty-input')
+export class MyPrettyInput extends LitElement {
+ @state() inputFocused = false;
+ @queryAssignedElements({selector: 'input'}) inputs!: Array;
+ #lastInput: HTMLInputElement | null = null;
+
+ render() {
+ return html`
+
+
+ The input is ${this.inputFocused ? '' : 'not'} focused
+
+ `;
+ }
+
+ #onSlotchange() {
+ // get the array of assigned elements and pick the first one
+ const firstInput = this.inputs[0];
+
+ if (firstInput !== this.#lastInput) {
+ this.#clearListeners();
+ }
+
+ if (!firstInput) {
+ return;
+ }
+
+ this.#lastInput = firstInput;
+
+ this.#attachListeners(firstInput);
+ }
+
+ #attachListeners(input: HTMLInputElement) {
+ input.addEventListener('focus', this.#onInputFocus);
+ input.addEventListener('blur', this.#onInputBlur);
+ }
+
+ #onInputFocus = () => {
+ this.inputFocused = true;
+ // get the array of assigned elements and pick the first one
+ const input = this.inputs[0];
+
+ input?.animate?.([
+ { 'transform': 'scale(1)', easing: 'ease-out' },
+ { 'transform': 'scale(2)', easing: 'ease-in' },
+ { 'transform': 'scale(1)' }
+ ],
+ 1000);
+ };
+
+ #onInputBlur = () => {
+ this.inputFocused = false;
+ const input = this.inputs[0];
+
+ input?.animate?.([
+ { 'transform': 'scale(1)', easing: 'ease-out' },
+ { 'transform': 'scale(.75)', easing: 'ease-in' },
+ { 'transform': 'scale(1)' }
+ ],
+ 1000);
+ };
+
+ #clearListeners() {
+ if (this.#lastInput) {
+ this.#lastInput.removeEventListener('focus', this.#onInputFocus);
+ this.#lastInput.removeEventListener('blur', this.#onInputBlur);
+ }
+
+ this.#lastInput = null;
+ }
+
+ static styles = css`/* playground-fold */
+ canvas {
+ border: 1px solid black;
+ }
+
+ label {
+ display: block;
+ margin-block-start: 1em;
+ }
+ /* playground-fold-end */`;
+}
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/dom-qae/project.json b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/dom-qae/project.json
new file mode 100644
index 000000000..5d8939af1
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/dom-qae/project.json
@@ -0,0 +1,8 @@
+{
+ "extends": "/samples/v3-base.json",
+ "files": {
+ "my-pretty-input.ts": {},
+ "index.html": {}
+ },
+ "previewHeight": "230px"
+}
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/dom-query-async/index.html b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/dom-query-async/index.html
new file mode 100644
index 000000000..57a0c9aca
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/dom-query-async/index.html
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/dom-query-async/my-element.ts b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/dom-query-async/my-element.ts
new file mode 100644
index 000000000..13e84b3a0
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/dom-query-async/my-element.ts
@@ -0,0 +1,68 @@
+import { html, LitElement, css, PropertyValues } from 'lit';
+import { customElement, state, property, queryAsync } from 'lit/decorators.js';
+
+@customElement('my-element')
+export class MyElement extends LitElement {
+ @property({type: Boolean}) showCanvas = false;
+ @state() private canvasText = 'Hello World!';
+ // You can use any querySelector selector
+ @queryAsync('#canvasId') private canvasEl!: Promise;
+
+ protected update(changed: PropertyValues) {
+ if (changed.has('showCanvas')) {
+ // Typically the DOM update isn't ready until after the render method
+ this.setCanvasText(this.canvasText);
+ }
+
+ super.update(changed);
+ }
+
+ render() {
+ return html`
+ ${
+ this.showCanvas ?
+ html`` :
+ html``
+ }
+
+ `;
+ }
+
+ private async setCanvasText(text: string) {
+ this.canvasText = text;
+ const canvasEl = await this.canvasEl;
+
+ // canvasEl can be null if the element is not in the DOM
+ if (!canvasEl) {
+ return;
+ }
+
+ const ctx = canvasEl?.getContext("2d");
+
+ if (!ctx) {
+ return;
+ }
+
+ ctx.clearRect(0, 0, canvasEl.width, canvasEl.height);
+ ctx.font = "50px Arial";
+ ctx.fillText(this.canvasText,10,80);
+ }
+
+ private handleInput(event: Event) {
+ this.setCanvasText((event.target as HTMLInputElement).value);
+ }
+
+ static styles = css`/* playground-fold */
+ canvas {
+ border: 1px solid black;
+ }
+
+ label {
+ display: block;
+ margin-block-start: 1em;
+ }
+ /* playground-fold-end */`;
+}
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/dom-query-async/project.json b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/dom-query-async/project.json
new file mode 100644
index 000000000..321475289
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/dom-query-async/project.json
@@ -0,0 +1,8 @@
+{
+ "extends": "/samples/v3-base.json",
+ "files": {
+ "my-element.ts": {},
+ "index.html": {}
+ },
+ "previewHeight": "230px"
+}
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/dom-query/index.html b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/dom-query/index.html
new file mode 100644
index 000000000..57a0c9aca
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/dom-query/index.html
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/dom-query/my-element.ts b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/dom-query/my-element.ts
new file mode 100644
index 000000000..94f5b06af
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/dom-query/my-element.ts
@@ -0,0 +1,53 @@
+import { html, LitElement, css } from 'lit';
+import { customElement, state, query } from 'lit/decorators.js';
+
+@customElement('my-element')
+export class MyElement extends LitElement {
+ @state() private canvasText = 'Hello World!';
+ // You can use any querySelector selector
+ @query('#canvasId') private canvasEl!: HTMLCanvasElement;
+
+ render() {
+ return html`
+
+
+ `;
+ }
+
+ private setCanvasText(text: string) {
+ this.canvasText = text;
+ // Access the canvas element with this.canvasEl
+ const ctx = this.canvasEl.getContext("2d");
+
+ if (!ctx) {
+ return;
+ }
+
+ ctx.clearRect(0, 0, this.canvasEl.width, this.canvasEl.height);
+ ctx.font = "50px Arial";
+ ctx.fillText(this.canvasText,10,80);
+ }
+
+ private handleInput(event: Event) {
+ this.setCanvasText((event.target as HTMLInputElement).value);
+ }
+
+ protected firstUpdated() {
+ // DOM is typically ready for the first time by firstUpdated()
+ this.setCanvasText(this.canvasText);
+ }
+
+ static styles = css`/* playground-fold */
+ canvas {
+ border: 1px solid black;
+ }
+
+ label {
+ display: block;
+ margin-block-start: 1em;
+ }
+ /* playground-fold-end */`;
+}
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/dom-query/project.json b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/dom-query/project.json
new file mode 100644
index 000000000..321475289
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/dom-query/project.json
@@ -0,0 +1,8 @@
+{
+ "extends": "/samples/v3-base.json",
+ "files": {
+ "my-element.ts": {},
+ "index.html": {}
+ },
+ "previewHeight": "230px"
+}
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/dom-ref/index.html b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/dom-ref/index.html
new file mode 100644
index 000000000..57a0c9aca
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/dom-ref/index.html
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/dom-ref/my-element.ts b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/dom-ref/my-element.ts
new file mode 100644
index 000000000..398013904
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/dom-ref/my-element.ts
@@ -0,0 +1,56 @@
+import { html, LitElement, css } from 'lit';
+import { customElement, state } from 'lit/decorators.js';
+import { ref, createRef } from 'lit/directives/ref.js';
+
+@customElement('my-element')
+export class MyElement extends LitElement {
+ @state() private canvasText = 'Hello World!';
+ private canvasRef = createRef();
+
+ render() {
+ return html`
+
+
+ `;
+ }
+
+ private setCanvasText(text: string) {
+ if (!this.canvasRef.value) {
+ return;
+ }
+ this.canvasText = text;
+ // Access the canvas element with this.canvasEl
+ const ctx = this.canvasRef.value.getContext("2d");
+
+ if (!ctx) {
+ return;
+ }
+
+ ctx.clearRect(0, 0, this.canvasRef.value.width, this.canvasRef.value.height);
+ ctx.font = "50px Arial";
+ ctx.fillText(this.canvasText,10,80);
+ }
+
+ private handleInput(event: Event) {
+ this.setCanvasText((event.target as HTMLInputElement).value);
+ }
+
+ protected firstUpdated() {
+ // DOM is typically ready for the first time by firstUpdated()
+ this.setCanvasText(this.canvasText);
+ }
+
+ static styles = css`/* playground-fold */
+ canvas {
+ border: 1px solid black;
+ }
+
+ label {
+ display: block;
+ margin-block-start: 1em;
+ }
+ /* playground-fold-end */`;
+}
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/dom-ref/project.json b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/dom-ref/project.json
new file mode 100644
index 000000000..321475289
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/dom-ref/project.json
@@ -0,0 +1,8 @@
+{
+ "extends": "/samples/v3-base.json",
+ "files": {
+ "my-element.ts": {},
+ "index.html": {}
+ },
+ "previewHeight": "230px"
+}
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/event-listeners/index.html b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/event-listeners/index.html
new file mode 100644
index 000000000..57a0c9aca
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/event-listeners/index.html
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/event-listeners/my-element.ts b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/event-listeners/my-element.ts
new file mode 100644
index 000000000..746840ccf
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/event-listeners/my-element.ts
@@ -0,0 +1,24 @@
+import { html, LitElement } from 'lit';
+import { customElement, query, state } from 'lit/decorators.js';
+
+@customElement('my-element')
+export class MyElement extends LitElement {
+ @state() inputValue = '';
+ @query('input') inputEl!: HTMLInputElement;
+
+ render() {
+ return html`
+
` : ''}
+ `;
+ }
+
+ connectedCallback(): void {
+ super.connectedCallback();
+
+ // Only want to do this in the browser since the server doesn't have the
+ // concept of events or document.
+ if (!isServer) {
+ document.addEventListener('click', this.onDocumentClick);
+ this.addEventListener('focusin', this.onFocusin);
+ this.addEventListener('focusout', this.onFocusout);
+ }
+ }
+
+ disconnectedCallback() {
+ super.disconnectedCallback();
+
+ if (!isServer) {
+ // clean up to prevent memory leaks
+ document.removeEventListener('click', this.onDocumentClick);
+ // Garbage should also take care of removing these, but it's good practice
+ this.removeEventListener('focusin', this.onFocusin);
+ this.removeEventListener('focusout', this.onFocusout);
+ }
+ }
+
+ // Should be an arrow function and not a class method to ensure `this` is
+ // bound correctly.
+ private onFocusin = () => {
+ this.focusedWithin = true;
+ };
+
+ private onFocusout = () => {
+ this.focusedWithin = false;
+ };
+
+ private onDocumentClick = (e: MouseEvent) => {
+ const path = e.composedPath();
+ this.clickedOutside = !path.includes(this);
+ };
+}
\ No newline at end of file
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/host-global-listeners/project.json b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/host-global-listeners/project.json
new file mode 100644
index 000000000..875a71aaf
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/host-global-listeners/project.json
@@ -0,0 +1,8 @@
+{
+ "extends": "/samples/v3-base.json",
+ "files": {
+ "my-element.ts": {},
+ "index.html": {}
+ },
+ "previewHeight": "120px"
+}
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/import-attributes/index.html b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/import-attributes/index.html
new file mode 100644
index 000000000..57a0c9aca
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/import-attributes/index.html
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/import-attributes/my-element.js b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/import-attributes/my-element.js
new file mode 100644
index 000000000..952c50945
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/import-attributes/my-element.js
@@ -0,0 +1,16 @@
+import { html, LitElement } from 'lit';
+import styles from './styles.css' with { type: 'css' };
+
+export class MyElement extends LitElement {
+ static styles = styles;
+
+ render() {
+ return html`
+
+ This should be red!
+
+ `;
+ }
+}
+
+customElements.define('my-element', MyElement);
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/import-attributes/project.json b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/import-attributes/project.json
new file mode 100644
index 000000000..f9ed532bd
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/import-attributes/project.json
@@ -0,0 +1,9 @@
+{
+ "extends": "/samples/v3-base.json",
+ "files": {
+ "my-element.js": {},
+ "styles.css": {},
+ "index.html": {}
+ },
+ "previewHeight": "100px"
+}
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/import-attributes/styles.css b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/import-attributes/styles.css
new file mode 100644
index 000000000..538fa56f4
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/import-attributes/styles.css
@@ -0,0 +1,3 @@
+div {
+ color: red;
+}
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/import/another-component.ts b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/import/another-component.ts
new file mode 100644
index 000000000..375269012
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/import/another-component.ts
@@ -0,0 +1,9 @@
+import { html, LitElement } from 'lit';
+import { customElement } from 'lit/decorators.js';
+
+@customElement('another-component')
+export class AnotherComponent extends LitElement {
+ render() {
+ return html`(I'm another component.)`;
+ }
+}
\ No newline at end of file
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/import/hello-world.ts b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/import/hello-world.ts
new file mode 100644
index 000000000..de5f2dccf
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/import/hello-world.ts
@@ -0,0 +1,10 @@
+import { html, LitElement } from 'lit';
+import { customElement } from 'lit/decorators.js';
+import './another-component.js'
+
+@customElement('hello-world')
+export class HelloWorld extends LitElement {
+ render() {
+ return html`Hello, world! `;
+ }
+}
\ No newline at end of file
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/import/index.html b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/import/index.html
new file mode 100644
index 000000000..c53974764
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/import/index.html
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/import/project.json b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/import/project.json
new file mode 100644
index 000000000..58dfe19ca
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/import/project.json
@@ -0,0 +1,9 @@
+{
+ "extends": "/samples/v3-base.json",
+ "files": {
+ "hello-world.ts": {},
+ "another-component.ts": {},
+ "index.html": {}
+ },
+ "previewHeight": "100px"
+}
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/inheriting-custom-props/index.html b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/inheriting-custom-props/index.html
new file mode 100644
index 000000000..5b273eb5d
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/inheriting-custom-props/index.html
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/inheriting-custom-props/my-element.ts b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/inheriting-custom-props/my-element.ts
new file mode 100644
index 000000000..f6ed8d2d6
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/inheriting-custom-props/my-element.ts
@@ -0,0 +1,17 @@
+import { html, LitElement, css } from 'lit';
+import { customElement } from 'lit/decorators.js';
+
+@customElement('my-element')
+export class MyElement extends LitElement {
+ static styles = css`
+ p {
+ color: var(--sys-color-on-background, blue);
+ border: 1px solid var(--sys-color-outline, black);
+ padding: var(--comp-my-element-padding, 4px);
+ margin-block: var(--comp-my-element-margin, 4px);
+ }
+ `;
+ render() {
+ return html`
This is in a shadow root!
`;
+ }
+}
\ No newline at end of file
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/inheriting-custom-props/project.json b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/inheriting-custom-props/project.json
new file mode 100644
index 000000000..875a71aaf
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/inheriting-custom-props/project.json
@@ -0,0 +1,8 @@
+{
+ "extends": "/samples/v3-base.json",
+ "files": {
+ "my-element.ts": {},
+ "index.html": {}
+ },
+ "previewHeight": "120px"
+}
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/pass-data-down/id-card.ts b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/pass-data-down/id-card.ts
new file mode 100644
index 000000000..b7a427e95
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/pass-data-down/id-card.ts
@@ -0,0 +1,20 @@
+import { html, LitElement } from 'lit';
+import { customElement, property } from 'lit/decorators.js';
+
+@customElement('id-card')
+export class IdCard extends LitElement {
+ @property() name = '';
+ @property({ type: Number }) age = 0;
+ @property({ type: Boolean }) programmer = false;
+
+ render() {
+ return html`
+
${this.name}
+
Age: ${this.age}
+
+ `;
+ }
+}
\ No newline at end of file
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/pass-data-down/index.html b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/pass-data-down/index.html
new file mode 100644
index 000000000..d5f0f24d5
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/pass-data-down/index.html
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/pass-data-down/my-wallet.ts b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/pass-data-down/my-wallet.ts
new file mode 100644
index 000000000..0159d6911
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/pass-data-down/my-wallet.ts
@@ -0,0 +1,12 @@
+import { html, LitElement } from 'lit';
+import { customElement } from 'lit/decorators.js';
+import './id-card.js';
+
+@customElement('my-wallet')
+export class MyWallet extends LitElement {
+ render() {
+ return html`
+
+ `;
+ }
+}
\ No newline at end of file
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/pass-data-down/project.json b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/pass-data-down/project.json
new file mode 100644
index 000000000..c8faab7cf
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/pass-data-down/project.json
@@ -0,0 +1,9 @@
+{
+ "extends": "/samples/v3-base.json",
+ "files": {
+ "my-wallet.ts": {},
+ "id-card.ts": {},
+ "index.html": {}
+ },
+ "previewHeight": "170px"
+}
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/reactive-properties/id-card.ts b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/reactive-properties/id-card.ts
new file mode 100644
index 000000000..0feccc9d3
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/reactive-properties/id-card.ts
@@ -0,0 +1,30 @@
+import { html, LitElement } from 'lit';
+import { customElement, property } from 'lit/decorators.js';
+
+@customElement('id-card')
+export class IdCard extends LitElement {
+ // Default attribute converter is string
+ @property() name = '';
+ // Number attribute converter converts attribtues to numbers
+ @property({ type: Number }) age = 0;
+ // Boolean attribute converter converts attribtues to boolean using
+ // .hasAttribute(). NOTE: boolean-attribute="false" will result in `true`
+ @property({ type: Boolean }) programmer = false;
+ // You can also specify the attribute name
+ @property({ type: Boolean, attribute: 'is-cool' }) isCool = false;
+
+ render() {
+ return html`
+
${this.name}
+
Age: ${this.age}
+
+
+ `;
+ }
+}
\ No newline at end of file
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/reactive-properties/index.html b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/reactive-properties/index.html
new file mode 100644
index 000000000..d5f0f24d5
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/reactive-properties/index.html
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/reactive-properties/my-wallet.ts b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/reactive-properties/my-wallet.ts
new file mode 100644
index 000000000..b88d3de0e
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/reactive-properties/my-wallet.ts
@@ -0,0 +1,19 @@
+import { html, LitElement } from 'lit';
+import { customElement } from 'lit/decorators.js';
+import './id-card.js';
+
+@customElement('my-wallet')
+export class MyWallet extends LitElement {
+ render() {
+ return html`
+
+
+
+
+
+ `;
+ }
+}
\ No newline at end of file
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/reactive-properties/project.json b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/reactive-properties/project.json
new file mode 100644
index 000000000..b768f2dcd
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/reactive-properties/project.json
@@ -0,0 +1,9 @@
+{
+ "extends": "/samples/v3-base.json",
+ "files": {
+ "id-card.ts": {},
+ "my-wallet.ts": {},
+ "index.html": {}
+ },
+ "previewHeight": "400px"
+}
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/reactive-state/index.html b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/reactive-state/index.html
new file mode 100644
index 000000000..57a0c9aca
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/reactive-state/index.html
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/reactive-state/my-element.ts b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/reactive-state/my-element.ts
new file mode 100644
index 000000000..d73ac0ef2
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/reactive-state/my-element.ts
@@ -0,0 +1,66 @@
+import { html, LitElement } from 'lit';
+import { customElement, state } from 'lit/decorators.js';
+
+@customElement('my-element')
+export class MyElement extends LitElement {
+ // Duration affects render, so it should be reactive. Though we don't want it
+ // to be exposed to consumers of my-element because we only want to expose
+ // `start()`, `pause()`, `reset()`, so we use a private state.
+ @state() private _duration = 0;
+ // isPlaying affects render, so it should be reactive. Though we don't want it
+ // to be exposed to consumers of my-element, so we use a private state.
+ @state() private _isPlaying = false;
+ private lastTick = 0;
+
+ render() {
+ const min = Math.floor(this._duration / 60000);
+ const sec = pad(min, Math.floor(this._duration / 1000 % 60));
+ const hun = pad(true, Math.floor(this._duration % 1000 / 10));
+
+ return html`
+
+ `;
+ }
+
+ private _sort(dir: number) {
+ this.tasks.sort((a, b) => a.label.localeCompare(b.label) * dir);
+ this.requestUpdate();
+ }
+}
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/repeat/project.json b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/repeat/project.json
new file mode 100644
index 000000000..89b03c57f
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/repeat/project.json
@@ -0,0 +1,8 @@
+{
+ "extends": "/samples/v3-base.json",
+ "files": {
+ "my-element.ts": {},
+ "index.html": {}
+ },
+ "previewHeight": "180px"
+}
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/rerender-array-change/index.html b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/rerender-array-change/index.html
new file mode 100644
index 000000000..57a0c9aca
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/rerender-array-change/index.html
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/rerender-array-change/my-element.ts b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/rerender-array-change/my-element.ts
new file mode 100644
index 000000000..dfb68d94d
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/rerender-array-change/my-element.ts
@@ -0,0 +1,43 @@
+import { html, LitElement } from 'lit';
+import { customElement, state } from 'lit/decorators.js';
+
+@customElement('my-element')
+export class MyElement extends LitElement {
+ // Technically `@state` is not necessary for the way we modify
+ // `_requestUpdateArray`, but it's generally good practice to use it.
+ @state() private _requestUpdateArray: number[] = [];
+ @state() private _newReferenceArray: number[] = [];
+
+ render() {
+ return html`
+
+ Request Update Array: [${this._requestUpdateArray.join(', ')}]
+
+
+
+
+
+
+ New Reference Array: [${this._newReferenceArray.join(', ')}]
+
+
+
+
+ `;
+ }
+
+ private _addToRequestUpdateArray() {
+ this._requestUpdateArray.push(this._requestUpdateArray.length);
+ // Call request update to tell Lit that something has changed.
+ this.requestUpdate();
+ }
+
+ private _addToNewReferenceArray() {
+ // This creates a new array / object reference, so it will trigger an update
+ // with the default change detection. Could be expensive for large arrays.
+ this._newReferenceArray = [
+ ...this._newReferenceArray,
+ this._newReferenceArray.length,
+ ];
+ }
+}
\ No newline at end of file
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/rerender-array-change/project.json b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/rerender-array-change/project.json
new file mode 100644
index 000000000..875a71aaf
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/rerender-array-change/project.json
@@ -0,0 +1,8 @@
+{
+ "extends": "/samples/v3-base.json",
+ "files": {
+ "my-element.ts": {},
+ "index.html": {}
+ },
+ "previewHeight": "120px"
+}
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/scope-styles/index.html b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/scope-styles/index.html
new file mode 100644
index 000000000..f22bdd0aa
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/scope-styles/index.html
@@ -0,0 +1,4 @@
+
+
+
+
I'm also a p, but I'm not blue.
\ No newline at end of file
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/scope-styles/my-element.ts b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/scope-styles/my-element.ts
new file mode 100644
index 000000000..65dae1c97
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/scope-styles/my-element.ts
@@ -0,0 +1,15 @@
+import { html, LitElement, css } from 'lit';
+import { customElement } from 'lit/decorators.js';
+
+@customElement('my-element')
+export class MyElement extends LitElement {
+ render() {
+ return html`
I'm blue
`;
+ }
+
+ static styles = css`
+ p {
+ color: blue;
+ }
+ `;
+}
\ No newline at end of file
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/scope-styles/project.json b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/scope-styles/project.json
new file mode 100644
index 000000000..816404c3d
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/scope-styles/project.json
@@ -0,0 +1,8 @@
+{
+ "extends": "/samples/v3-base.json",
+ "files": {
+ "my-element.ts": {},
+ "index.html": {}
+ },
+ "previewHeight": "100px"
+}
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/shadow-root-options/index.html b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/shadow-root-options/index.html
new file mode 100644
index 000000000..57a0c9aca
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/shadow-root-options/index.html
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/shadow-root-options/my-element.ts b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/shadow-root-options/my-element.ts
new file mode 100644
index 000000000..fd5901705
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/shadow-root-options/my-element.ts
@@ -0,0 +1,28 @@
+import { html, LitElement, css } from 'lit';
+import { customElement } from 'lit/decorators.js';
+
+@customElement('my-element')
+export class MyElement extends LitElement {
+ static override shadowRootOptions = {
+ ...LitElement.shadowRootOptions,
+ delegatesFocus: true,
+ };
+
+ render() {
+ return html`
+
+ Calling focus on this element will focus the first focusable element in
+ its shadow root thanks to delegatesFocus: true. Just try
+ clicking on this text and see how the input is focused instead.
+
+ Lorem, ipsum dolor sit amet consectetur adipisicing elit. Minus soluta atque
+ blanditiis maxime ex veritatis accusantium, ea sequi deleniti expedita
+ recusandae totam obcaecati ipsum dicta cum et. Consequuntur, autem
+ consectetur.
+
+
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/shadow-slotting/my-card.ts b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/shadow-slotting/my-card.ts
new file mode 100644
index 000000000..ffd17b670
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/shadow-slotting/my-card.ts
@@ -0,0 +1,66 @@
+import { html, LitElement, css } from 'lit';
+import { customElement} from 'lit/decorators.js';
+
+@customElement('my-card')
+export class MyElement extends LitElement {
+
+ render() {
+ return html`
+
+ This is a direct child and should have a solid border styled with
+ ::slotted(*).
+
+
+ This is a child of a direct child and cannot be styled directly with
+ ::slotted(*). Therefore my-element should
+ style known CSS custom properties which should be applied in the light DOM.
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
+ `;
+ }
+
+ static styles = css`
+ #text {
+ max-width: 100%;
+ border: 1px solid black;
+ }
+ `;
+}
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/update-complete/project.json b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/update-complete/project.json
new file mode 100644
index 000000000..00842cd85
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/update-complete/project.json
@@ -0,0 +1,8 @@
+{
+ "extends": "/samples/v3-base.json",
+ "files": {
+ "my-element.ts": {},
+ "index.html": {}
+ },
+ "previewHeight": "300px"
+}
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/virtualizer/index.html b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/virtualizer/index.html
new file mode 100644
index 000000000..57a0c9aca
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/virtualizer/index.html
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/virtualizer/my-element.ts b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/virtualizer/my-element.ts
new file mode 100644
index 000000000..8161cfaaf
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/virtualizer/my-element.ts
@@ -0,0 +1,23 @@
+import {LitElement, html} from 'lit';
+import {customElement} from 'lit/decorators.js';
+import '@lit-labs/virtualizer';
+
+@customElement('my-element')
+export class MyItems extends LitElement {
+ data = new Array(10000).fill('').map((i, n) => ({text: `Item ${n}`}));
+
+ render() {
+ return html`
+
+
+ html`
${i.text}
`}>
+
+
+ `;
+ }
+}
diff --git a/packages/lit-dev-content/samples/articles/lit-cheat-sheet/virtualizer/project.json b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/virtualizer/project.json
new file mode 100644
index 000000000..00842cd85
--- /dev/null
+++ b/packages/lit-dev-content/samples/articles/lit-cheat-sheet/virtualizer/project.json
@@ -0,0 +1,8 @@
+{
+ "extends": "/samples/v3-base.json",
+ "files": {
+ "my-element.ts": {},
+ "index.html": {}
+ },
+ "previewHeight": "300px"
+}
diff --git a/packages/lit-dev-content/samples/examples/context-consume-provide/project.json b/packages/lit-dev-content/samples/examples/context-consume-provide/project.json
index 2f44284be..62e3c7d17 100644
--- a/packages/lit-dev-content/samples/examples/context-consume-provide/project.json
+++ b/packages/lit-dev-content/samples/examples/context-consume-provide/project.json
@@ -9,5 +9,6 @@
"my-heading.ts": {},
"level-context.ts": {},
"index.html": {}
- }
+ },
+ "previewHeight": "600px"
}
diff --git a/packages/lit-dev-content/site/_includes/articles.html b/packages/lit-dev-content/site/_includes/articles.html
index 3beccf0fb..66c4b431b 100644
--- a/packages/lit-dev-content/site/_includes/articles.html
+++ b/packages/lit-dev-content/site/_includes/articles.html
@@ -15,6 +15,7 @@
+
{% endblock %}
{% block content %}
diff --git a/packages/lit-dev-content/site/articles/article/lit-cheat-sheet.md b/packages/lit-dev-content/site/articles/article/lit-cheat-sheet.md
new file mode 100644
index 000000000..8dbac9f2a
--- /dev/null
+++ b/packages/lit-dev-content/site/articles/article/lit-cheat-sheet.md
@@ -0,0 +1,1041 @@
+---
+title: The Lit Cheat Sheet
+publishDate: 2024-05-10
+lastUpdated: 2024-05-10
+summary: A quick reference for the basics of Lit
+thumbnail: /images/articles/lit_cheat_sheet
+tags:
+ - web-components
+ - web-components
+eleventyNavigation:
+ parent: Articles
+ key: The Lit Cheat Sheet
+ order: 1
+author:
+ - elliott-marquez
+preloadTsWorker: true
+---
+
+Do you need a quick reference for the basics of Lit? Look no further! This cheat
+sheet will help you get started with, or just remember, the features of Lit.
+
+If you are coming from another framework, you might also want to supplement this
+article with [Component Party](https://component-party.dev/) which compares
+basic concepts across different frameworks. Just make sure that Lit is selected
+at the top of the page!
+
+{% aside "positive" "no-header" %}
+
+Use the Table of Contents to jump to a specific section!
+
+{% endaside %}
+
+## Component Definition
+
+### Defining a Component
+
+`LitElement` is the base class for all Lit components.
+
+@customElementcustomElements.define is where you associate the name of your component with the class definition / logic of that component.
+
+`render()` is the method where you define your component's template using a tagged template literal with `html`.
+
+Write your HTML in the `html` tagged template literal.
+
+{% playground-ide "articles/lit-cheat-sheet/define", true %}
+
+
+{% aside "info" %}
+
+Important rules:
+
+Components are global HTML elements, you currently can't have more than one with the same name on a page.
+
+Components must have dashes in their name (defined using @customElementcustomElements.define).
+
+{% endaside %}
+
+
+**Related Documentation & Topics:**
+
+- [Defining a Component](/docs/components/defining/)
+- [LitElement](/docs/api/LitElement/)
+- [`@customElement`](/docs/api/decorators/#customElement)
+- [`customElements.define`](https://developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry/define)
+- [Rendering](/docs/components/rendering/)
+- [How to build your first Lit component](https://www.youtube.com/watch?v=QBa1_QQnRcs) (Video)
+- [How to style your Lit elements](https://www.youtube.com/watch?v=Xt7blcyuw5s)
+
+### Import a component
+
+To use a component, import the file with its definition.
+
+{% playground-ide "articles/lit-cheat-sheet/import", true %}
+
+**Related Documentation & Topics:**
+
+- [`import`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import) (external)
+- [`import()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import) (external)
+- [JavaScript modules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules) (external)
+- [`@customElement`](/docs/api/decorators/#customElement)
+- [`customElements.define`](https://developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry/define) (external)
+- [Local dev servers (bare module specifiers)](/docs/tools/development/#devserver)
+
+## Templating
+
+### Render HTML
+
+Use the `html` tagged template literal to define your component's template.
+
+{% playground-ide "articles/lit-cheat-sheet/define", true %}
+
+**Related Documentation & Topics:**
+
+- [Templates Overview](/docs/templates/overview/)
+- [Rendering](/docs/components/rendering/)
+
+### Conditionals
+
+Use standard JavaScript conditional expressions in your template to conditionally render content.
+
+{% playground-ide "articles/lit-cheat-sheet/conditionals", true %}
+
+**Related Documentation & Topics:**
+
+- [Conditionals](/docs/templates/conditionals/)
+- [Expressions](/docs/templates/expressions/)
+
+### Attribute and Property Expressions (Binding syntax)
+
+Lit-html has three types of built-in expressions to set attributes or properties on elements:
+
+- Property expressions `.prop=${value}`
+- Attribute expressions `attr=${value}`
+- Boolean attribute expressions `?attr=${value}`
+
+{% playground-ide "articles/lit-cheat-sheet/expressions", true %}
+
+**Related Documentation & Topics:**
+
+- [Expressions](/docs/templates/expressions/)
+- [Expressions – Attribute Expressions](/docs/templates/expressions/#attribute-expressions)
+- [Expressions – Property Expressions](/docs/templates/expressions/#property-expressions)
+
+### Event Listener Expressions
+
+Lit-html has a built-in event listener expression to add event listeners to
+elements. You can also use event listeners to mimic two-way databinding with
+input elements.
+
+{% playground-ide "articles/lit-cheat-sheet/event-listeners", true %}
+
+**Related Documentation & Topics:**
+
+- [Expressions](/docs/templates/expressions/)
+- [Expressions – Event listener expressions](/docs/templates/expressions/#event-listener-expressions)
+- [Events](/docs/components/events/)
+- [Event communication between web components](https://www.youtube.com/watch?v=T9mxtnoy9Qw&themeRefresh=1) (video)
+- [Customizing event listener options](/docs/components/events/#event-options-decorator)
+- [A complete guide on shadow DOM and event propagation](https://pm.dartus.fr/posts/2021/shadow-dom-and-event-propagation/) (external)
+
+### Rendering lists
+
+Lit-html can render JavaScript arrays and iterables. For most simple use cases,
+you can use the `Array.map()` method to render arrays of items or the `map()`
+directive to render any other iterables. This pattern is best used for short,
+simple lists.
+
+{% playground-ide "articles/lit-cheat-sheet/render-lists", true %}
+
+**Related Documentation & Topics:**
+
+- [Lists](/docs/templates/lists/)
+- [Working With Lists Tutorial](/tutorials/working-with-lists/)
+- [Built-in directives – `map()`](/docs/templates/directives/#map)
+- [Built-in directives – `range()`](/docs/templates/directives/#range)
+- [Built-in directives – `join()`](/docs/templates/directives/#join)
+
+### Re-rendering lists efficiently
+
+For long lists that may change frequently, use the `repeat()` directive to efficiently re-render only the items that have changed.
+
+{% playground-ide "articles/lit-cheat-sheet/repeat", true %}
+
+- [Lists](/docs/templates/lists/)
+- [Working With Lists Tutorial – The `repeat()` directive](/tutorials/working-with-lists/#6)
+- [Built-in directives – `repeat()`](/docs/templates/directives/#repeat)
+
+### Virtualizing long lists
+
+For lists that are so long that it would be impractical to render all items at once, use the Lit Virtualizer to render only the items that are currently in view.
+
+{% playground-ide "articles/lit-cheat-sheet/virtualizer", true %}
+
+{% aside "labs" %}
+
+Lit Virtualizer is in labs
+
+meaning that its implementation might change until it has graduated and become stable. Additionally there are many more features to virtualizer, so it is recommended to look at the documentation.
+
+{% endaside %}
+
+**Related Documentation & Topics:**
+
+- [Lit Virtualizer Documentation](https://github.com/lit/lit/tree/main/packages/labs/virtualizer#readme) (external)
+- [Lit Labs: Virtualizer](https://www.youtube.com/watch?v=ay8ImAgO9ik) (video)
+
+### Render String to HTML
+
+To render a string of HTML as HTML in Lit, use the `unsafeHTML` directive.
+
+{% playground-ide "articles/lit-cheat-sheet/unsafe-html", true %}
+
+{% aside "negative" %}
+
+Be careful when using `unsafeHTML` as it can open your application up to
+cross-site scripting (XSS) and other attacks.
+
+Only use `unsafeHTML` with trusted sources and strings as you would use
+`Element.prototype.innerHTML`.
+
+{% endaside %}
+
+**Related Documentation & Topics:**
+
+- [Built-in directives – `unsafeHTML`](/docs/templates/directives/#unsafehtml)
+- [innerHTML Security considerations](https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML#security_considerations) (external)
+
+### Bind to HTML Tag Name
+
+In rare cases, you need to bind to an HTML tag name to change the rendered
+element. You can do this safely with the `static-html` module and the `literal`
+template tag.
+
+{% playground-ide "articles/lit-cheat-sheet/bind-tag-name", true %}
+
+
+{% aside "warn" %}
+
+Be careful when using static HTML templates as changing values of a static
+template is expensive as they are uncached.
+
+In most cases it is recommended to use conditional template rendering instead of
+static HTML templates.
+
+{% endaside %}
+
+**Related Documentation & Topics:**
+
+- [Static Expressions](/docs/templates/expressions/#static-expressions)
+- [`live()` directive](/docs/templates/directives/#live)
+
+### Bind Any Value to HTML Tag Name or Attribute Name
+
+In even rarer cases, you need to bind any arbitrary string value to an HTML tag
+name to change the rendered element. You can do this with the `unsafeStatic()`
+directive. This may be helpful if you are implementing an SSR framework that
+uses lit-html for rendering.
+
+{% playground-ide "articles/lit-cheat-sheet/unsafe-static", true %}
+
+{% aside "negative" %}
+
+Be careful when using `unsafeStatic` as it can open your application up to
+cross-site scripting (XSS) and other attacks.
+
+Only use `unsafeStatic` with trusted sources and strings as you would use
+`Element.prototype.innerHTML`. Additionally, `unsafeStatic` is not cached and
+will re-render the entire template every time the value changes which may
+negatively affect performance.
+
+{% endaside %}
+
+- [Non-literal Statics](/docs/templates/expressions/#non-literal-statics)
+- [Static Expressions](/docs/templates/expressions/#static-expressions)
+- [`live()` directive](/docs/templates/directives/#live)
+
+## Styles
+
+### Add Styles
+
+Add styles by defining the `static styles` property. Write CSS in the `css` tagged template literal.
+
+{% playground-ide "articles/lit-cheat-sheet/add-styles", true %}
+
+**Related Documentation & Topics:**
+
+- [Styles](/docs/components/styles/)
+
+### Styles are Scoped
+
+Styles *only* apply to the current element. That means you can feel free to use super generic selectors that you'd normally have to make up class names for.
+
+{% playground-ide "articles/lit-cheat-sheet/scope-styles", true %}
+
+**Related Documentation & Topics:**
+
+- [Styles](/docs/components/styles/)
+- [Shadow DOM](/docs/components/shadow-dom/)
+
+### Conditionally Add Classes
+
+To conditionally apply styles it's generally best to use `classMap`.
+
+{% playground-ide "articles/lit-cheat-sheet/classes", true %}
+
+**Related Documentation & Topics:**
+
+- [Defining Scoped Styles in the template](/docs/components/styles/#styles-in-the-template)
+- [`classMap`](/docs/templates/directives/#classmap)
+- [`classMap` tsdoc](/docs/api/directives/#classMap)
+- [Playground example](/playground/#sample=examples/directive-class-map)
+
+### Sharing Lit styles with imports
+
+You can share Lit stylesheets with other components by exporting them from a module and importing them into another.
+
+{% playground-ide "articles/lit-cheat-sheet/share-styles-import", true %}
+
+- [Styling](/docs/components/styles/)
+- [Sharing Styles](/docs/components/styles/#sharing-styles)
+
+### Inheriting Styles Through Shadow DOM With CSS Custom Properties
+
+CSS Custom Properties can pierce multiple shadow roots allowing you to share values for specific properties.
+
+{% playground-ide "articles/lit-cheat-sheet/inheriting-custom-props", true %}
+
+**Related Documentation & Topics:**
+
+- [CSS Custom Properties](/docs/components/styles/#customprops)
+- [Theming](/docs/components/styles/#theming)
+- [How to style your Lit elements](https://www.youtube.com/watch?v=Xt7blcyuw5s) (Video)
+
+### Setting Arbitrary Styles With CSS Shadow Parts
+
+CSS Shadow Parts are exposed by components with the `part=""` attribute.
+
+Shadow Parts can pierce individual shadow roots allowing you to set arbitrary styles on a given node using the `::part()` pseudo-element.
+
+{% playground-ide "articles/lit-cheat-sheet/css-shadow-parts", true %}
+
+**Related Documentation & Topics:**
+
+- [How to style your Lit elements](https://www.youtube.com/watch?v=Xt7blcyuw5s) (video)
+- [CSS Shadow Parts](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_shadow_parts) (external)
+- [`::part`](https://developer.mozilla.org/en-US/docs/Web/CSS/::part) (external)
+
+### Exporting a CSS Shadow Part
+
+CSS Shadow part names can only apply to the targeted element. You need to use `exportparts` to expose a shadow part in nested shadow roots.
+
+You can export multiple parts by separating them with a comma (`,`).
+
+You can also rename parts with a colon (`:`).
+
+{% playground-ide "articles/lit-cheat-sheet/export-part", true %}
+
+**Related Documentation & Topics:**
+
+- [`exportparts`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/exportparts) (external)
+- [How to style your Lit elements](https://www.youtube.com/watch?v=Xt7blcyuw5s) (video)
+- [CSS Shadow Parts](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_shadow_parts) (external)
+- [`::part`](https://developer.mozilla.org/en-US/docs/Web/CSS/::part) (external)
+
+### Importing Styles as a String
+
+In some rare cases, you may receive trusted styles as a string and may need to
+apply them to a component. You can do this with native, constructable
+stylesheets.
+
+{% playground-ide "articles/lit-cheat-sheet/constructable-stylesheets", true %}
+
+{% aside "negative" %}
+
+Be careful when using constructable stylesheets as it can open your application
+to privacy and security vulnerabilities.
+
+Only use constructable stylesheets with trusted sources and strings.
+
+{% endaside %}
+
+**Related Documentation & Topics:**
+
+- [Constructable Stylesheets](https://web.dev/articles/constructable-stylesheets) (external)
+- [`CSSResultOrNative`](/docs/api/styles/#CSSResultOrNative)
+
+### Importing CSS Files into Components
+
+In some cases, you may want to import styles in the form of a CSS files rather
+than a Lit CSSResult or a string. Currently there
+
+{% aside "warn" %}
+
+This is a new feature recently added to some browsers.
+
+Please check [browser compatibility on MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import/with#browser_compatibility).
+
+Due to the newness of this feature, the following example uses JavaScript and
+may not work in all in certain browsers.
+
+{% endaside %}
+
+{% playground-ide "articles/lit-cheat-sheet/import-attributes", true %}
+
+{% aside "positive" %}
+
+Some better-supported alternatives to this approach may include:
+
+- A build tool plugin that transforms your CSS imports into Lit `CSSResult`
+ similar to
+ [`rollup-plugin-lit-css`](https://www.npmjs.com/package/rollup-plugin-lit-css)
+- Using a bundler tool to transform your CSS imports into strings and then using
+ Constructable Stylesheets
+- Using a `` in your template, but this will
+ cause FOUC.
+
+{% endaside %}
+
+**Related Documentation & Topics:**
+
+- [Import Attributes](https://v8.dev/features/import-attributes) (external)
+- [Import Attributes Browser Compatiblity Table](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import/with#browser_compatibility) (external)
+- [Proposal Import Attributes](https://github.com/tc39/proposal-import-attributes) (external)
+- [Import JSON, CSS and more with import attributes](https://fullystacked.net/import-attributes/) (external)
+- [Using CSS Module Scripts to import stylesheets](https://web.dev/articles/constructable-stylesheets) (external)
+ - **NOTE:** The syntax of the import assertions proposal in this article was
+ deprecated in favor of import attribtues which uses `with` rather than
+ `assert`.
+- Build Tools
+ - [`rollup-plugin-lit-css`](https://www.npmjs.com/package/rollup-plugin-lit-css)
+ - [`esbuild-plugin-lit-css`](https://www.npmjs.com/package/esbuild-plugin-lit-css)
+ - [`lit-css-loader`](https://www.npmjs.com/package/lit-css-loader)
+ - [`vite-plugin-lit-css`](https://www.npmjs.com/package/vite-plugin-lit-css)
+
+## Shadow DOM
+
+### What does Shadow DOM do?
+
+- Scopes styles to the shadow root
+- Scopes the DOM to the shadow root
+ - Can't be targeted by querySelector calls from outside the shadow root
+- Enables slotting content with the `` element
+- Exposes an API for CSS with CSS Custom Properties and CSS Shadow Parts
+
+**Related Documentation & Topics:**
+
+- [DOM Encapsulation](/docs/components/rendering/#dom-encapsulation)
+- [Working with Shadow DOM](/docs/components/shadow-dom/)
+- [How to style your Lit elements](https://www.youtube.com/watch?v=Xt7blcyuw5s) (video)
+
+### Turn off Shadow DOM
+
+You can turn off the Shadow DOM by overriding the `createRenderRoot()` method and setting the render root to the element itself.
+
+{% playground-ide "articles/lit-cheat-sheet/turn-off-shadow-dom", true %}
+
+{% aside "warn" %}
+
+By turning off shadow DOM you lose the benefits of encapsulation, DOM scoping, and `` elements.
+
+Since the Shadow root no longer exists, Lit will no longer handle the `static styles` property for you. You must decide how to handle your styles.
+
+{% endaside %}
+
+**Related Documentation & Topics:**
+
+- [Implementing `createRenderRoot()`](/docs/components/shadow-dom/#implementing-createrenderroot)
+
+### Slotting Components Into Another Component's Shadow DOM
+
+You can slot a component into another component's shadow DOM by using the
+`` element. If you're familiar with React, this is similar to
+`props.children`.
+
+{% playground-ide "articles/lit-cheat-sheet/shadow-slotting", true %}
+
+**Related Documentation & Topics:**
+
+- [Slots](/docs/components/shadow-dom/#slots)
+- [Working With Shadow DOM](/docs/components/shadow-dom/)
+- [How to style your Lit elements](https://www.youtube.com/watch?v=Xt7blcyuw5s) (video)
+- [How to Build a Carousel in Lit](https://www.youtube.com/watch?v=2RftvylEtrE) (video)
+- [Build an Animated Carousel Tutorial](/tutorials/carousel)
+
+### Styling a Slotted Component
+
+Slotted components use the browser's native Shadow DOM projection features. In
+order to keep strong, performant, and encapsulated styles, the browser vendors
+have placed restrictions on styling slotted content.
+
+You can style directly-slotted elements with the `::slotted()` pseudo-selector.
+If you would like to style children of slotted content, you should use CSS
+Custom Properties.
+
+{% playground-ide "articles/lit-cheat-sheet/styling-slotted", true %}
+
+**Related Documentation & Topics:**
+
+- [Styling the Component's Children](/docs/components/styles/#slotted)
+- [Build an animated carousel element tutorial](/tutorials/carousel)
+- [How to style your Lit elements](https://www.youtube.com/watch?v=Xt7blcyuw5s) (video)
+- [How to build a Carousel in Lit](https://www.youtube.com/watch?v=2RftvylEtrE&t) (video)
+
+### Turning on `delegatesFocus` and other shadow root options
+
+You can set shadow root options passed to `Element.attachShadow()` by overriding the static `shadowRootOptions` member.
+
+{% playground-ide "articles/lit-cheat-sheet/shadow-root-options", true %}
+
+**Related Documentation & Topics:**
+
+- [Setting `shadowRootOptions`](/docs/components/shadow-dom/#setting-shadowrootoptions)
+- [`Element.prototype.attachShadow():options`](https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow#options) (external)
+- [`delegatesFocus`](https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot/delegatesFocus) (external)
+
+## Properties and State
+
+### Reactive Properties
+
+Reactive properties are properties within a component that automatically trigger
+a re-render when they change. These properties can be set externally, from
+outside the component's boundary.
+
+They also handle attributes by accepting them and converting them into
+corresponding properties.
+
+You can define a reactive property with the @property decoratorstatic properties = { propertyName: {...}} code block and initializing them in the constructor().
+
+
+{% playground-ide "articles/lit-cheat-sheet/reactive-properties", true %}
+
+**Related Documentation & Topics:**
+
+- [Reactive Properties](/docs/components/properties/)
+- [Public reactive properties](/docs/components/properties/#declare)
+- [Attributes](/docs/components/properties/#attributes)
+- [Custom property accessors](/docs/components/properties/#attributes)
+- [Customizing change detection](/docs/components/properties/#haschanged)
+- [Reactivity Tutorial](/tutorials/reactivity/)
+- [Custom Attribute Converters Tutorial](/tutorials/custom-attribute-converter/)
+- [What Are Elements Video](https://www.youtube.com/watch?v=x_mixcGEia4)
+- [Introduction to Lit - Reactive Properties Video](https://www.youtube.com/watch?v=uzFakwHaSmw&t=576s)
+
+### Reactive State
+
+Reactive state is a property that is private to the component and is not exposed
+to the outside world. These properties are used to store internal state of a
+component that should trigger a re-render of the Lit lifecycle when they change.
+
+You can define a reactive property with the @state decoratorstatic properties = { propertyName: {state: true, ...}} code block and setting the state: true flag in the property's info. You can initialize them in the constructor().
+
+{% playground-ide "articles/lit-cheat-sheet/reactive-state", true %}
+
+**Related Documentation & Topics:**
+
+- [Reactive Properties](/docs/components/properties/)
+- [Internal Reactive State](/docs/components/properties/#internal-reactive-state)
+- [Customizing change detection](/docs/components/properties/#haschanged)
+- [Reactivity Tutorial](/tutorials/reactivity/)
+- [What Are Elements Video](https://www.youtube.com/watch?v=x_mixcGEia4)
+- [Introduction to Lit - Reactive Properties Video](https://www.youtube.com/watch?v=uzFakwHaSmw&t=576s)
+
+### Re-render an Array or Object Change
+
+Arrays are objects in JavaScript, and Lit's default change detection uses strict
+equality to determine if an array has changed. If you need to re-render a
+component when an array is mutated with something like `.push()` or `.pop()`,
+you will need to let Lit know that the array has changed.
+
+The most common ways to do this are:
+
+- Use the `requestUpdate()` method to manually trigger a re-render
+- Create a new array / object reference
+
+{% playground-ide "articles/lit-cheat-sheet/rerender-array-change", true %}
+
+{% aside "warn" %}
+
+Custom `hasChanged()` methods in the reactive property definition won't help
+much here.
+
+The `hasChanged()` function is only called when the property is set, not when
+the property is mutated. This would only be helpful when an array or object has
+a new reference assigned to it and you _don't_ want to trigger a re-render.
+
+If this is your use case, you might generally be better off using a
+[`repeat()` directive](#re-rendering-lists-efficiently).
+
+{% endaside %}
+
+**Related Documentation & Topics:**
+
+- [Lifecycle - Triggering an update](/docs/components/lifecycle/#reactive-update-cycle-triggering)
+- [Rendering Arrays](/docs/templates/lists/#rendering-arrays)
+- [Reactivity Tutorial - Triggering an update](/tutorials/reactivity/#3)
+- [Working With Lists Tutorial](/tutorials/working-with-lists/)
+
+### Custom Attribute Converters
+
+In advanced cases, you may need to convert an attribute value to a property in
+a special way and vice versa. You can do this with a custom attribute converter.
+
+{% playground-ide "articles/lit-cheat-sheet/custom-attribute-converter", true %}
+
+**Related Documentation & Topics:**
+
+- [Reactive properties - Providing a custom converter](/docs/components/properties/#conversion-converter)
+- [Reactive properties - Using the default converter](/docs/components/properties/#conversion-type)
+- [Attributes](/docs/components/properties/#attributes)
+- [Custom Attribute Converters Tutorial](/tutorials/custom-attribute-converter/)
+- [Reactive Properties](/docs/components/properties/)
+- [Public reactive properties](/docs/components/properties/#declare)
+- [Custom attribute converter snippet](/playground/#sample=examples/properties-custom-converter)
+
+### Context
+
+If you need to pass data down to a subtree without using properties or "prop
+drilling", you might want to use
+[`@lit/context`](https://www.npmjs.com/package/@lit/context).
+
+{% playground-ide "examples/context-consume-provide", true %}
+
+**Related Documentation & Topics:**
+
+- [Context](/docs/data/context/)
+- [WCCG Context Community Protocol](https://github.com/webcomponents-cg/community-protocols/blob/main/proposals/context.md) (external)
+
+## Lifecycle
+
+### Lifecycle order
+
+There are two lifecycles in Lit, the native Web Component lifecycle and the
+lifecycle that Lit adds on top of it to help handle property and state changes.
+
+There are more lifecycle events which can be
+[found in the documentation](/docs/components/lifecycle/#reactive-update-cycle),
+but the ones you would normally use are the following and this is their general
+order:
+
+1. `constructor` – (Native custom element lifecycle)
+2. `connectedCallback` – (Native)
+3. `willUpdate` – (Lit lifecycle)
+4. `update` – (Lit)
+5. `render` – (Lit)
+6. `firstUpdated` – (Lit)
+7. `updated` – (Lit)
+8. `disconnectedCallback` – (Native)
+
+{% aside "warn" %}
+
+The Lit lifecycle and the native custom element lifecycle are distinct and managed separately.
+
+This means that while they generally follow a specific order, they may intermix
+because the browser controls the native lifecycle, while Lit and JavaScript
+manage the Lit lifecycle.
+
+For example, a component may be attached to the DOM and then removed before the
+Lit lifecycle may run at all, or a component may be created with
+`document.createElement` which would call the `constructor`, but if it's never
+added to the DOM, the `connectedCallback` would never run and thus the Lit
+lifecycle would never run.
+
+{% endaside %}
+
+**Related Documentation & Topics:**
+
+- [Lifecycle](/docs/components/lifecycle/)
+- [Lifecycle diagram](/docs/components/lifecycle/#reactive-update-cycle)
+
+#### `constructor`
+
+- Runs when the element is created via:
+ - `document.createElement('my-element')`
+ - `element.innerHTML = ''`
+ - `new MyElement()`
+ - etc.
+- A good place to set initial properties
+- Do **NOT** require parameters in the constructor or modify DOM
+
+#### `connectedCallback`
+
+- *May* run on the server. This has not been finalized.
+- Runs when the element is added to the DOM via:
+ - `element.appendChild(element)`
+ - `element.innerHTML = ''`
+ - etc.
+- Can run multiple times, but a good place to set up event listeners
+
+#### `willUpdate`
+
+- Runs on the server – do **NOT** access the DOM or browser APIs
+- A good place to set up properties that depend on other properties
+
+#### `update`
+
+- A good place to update properties that depend on other properties that depend on DOM
+
+#### `render`
+
+- Runs on the server - do NOT access the DOM or browser APIs
+
+#### `firstUpdated`
+
+- Runs after the first render
+- A good place to perform initializations that require access to the component's
+ rendered DOM
+
+#### `updated`
+
+- A good place to do some updates that require the component's rendered DOM or
+ to update properties that depend on the rendered DOM
+- Avoid setting reactive properties in this lifecycle as doing so may trigger
+ unnecessary re-renders. Try to do them in `willUpdate` or `update` if
+ possible.
+
+#### `disconnectedCallback`
+
+- Is called *AFTER* the element is removed from the DOM
+- A good place to clean up event listeners
+
+### Waiting for an update
+
+All Lit elements have asynchronous lifecycles. You need to wait for the `updateComplete` promise to resolve before you can be sure that the element has finished updating its DOM.
+
+{% playground-ide "articles/lit-cheat-sheet/update-complete", true %}
+
+**Related Documentation & Topics:**
+
+- [Lifecycle](/docs/components/lifecycle/)
+- [updateComplete()](/docs/components/lifecycle/#updatecomplete)
+- [requestUpdate()](/docs/components/lifecycle/#requestUpdate)
+- [Getting an Element Reference](#getting-an-element-reference)
+- [Getting an Element Reference After Update](#getting-an-element-reference-after-update)
+
+### Asynchronous Tasks
+
+If you need to perform an asynchronous task in a Lit Element. You may want to
+use the `@lit/task` package. It handles marrying basics of managing asynchronous
+and the Lit lifecycle.
+
+The following example fetches a Pokemon by ID from the
+[PokeAPI](https://pokeapi.co/) based on pokemon name. To do so you:
+
+1. Initialize the task with `new Task(...)`
+2. Task is a Reactive Controller, so you pass it a reference to the Reactive
+ Element (`this`)
+3. Write an async function that fetches and returns the data
+4. Give Task a function that will return the reactive properties that Task
+ relies on
+5. Render all the states of the Task in the `render()` method with
+ `Task.prototype.render()`
+
+{% playground-ide "articles/lit-cheat-sheet/task", true %}
+
+**Related Documentation & Topics:**
+
+- [Asynchronous Tasks](/docs/data/task/)
+- [Lit Labs – Task](https://www.youtube.com/watch?v=niWKuGhyE0M) (video)
+ - NOTE: This video was published before Task had graduated from Labs.
+- [Reactive Controllers – Asynchronous Tasks](/docs/composition/controllers/#asynchronous-tasks)
+
+## Events
+
+### Adding listeners to host element or globally
+
+A common pattern is to add event listeners to the host element or globally in the `connectedCallback` and remove them in the `disconnectedCallback`.
+
+{% playground-ide "articles/lit-cheat-sheet/host-global-listeners", true %}
+
+**Related Documentation & Topics:**
+
+- [Lifecycle](/docs/components/lifecycle/)
+- [Events](/docs/components/events/)
+- [Authoring components for Lit SSR](/docs/ssr/authoring/)
+- [A complete guide on shadow DOM and event propagation](https://pm.dartus.fr/posts/2021/shadow-dom-and-event-propagation/) (external)
+
+## Data Flow and State Management
+
+### Pass data down
+
+The simplest way to pass data down is to use properties and attributes.
+
+For example, you can pass data down to child components using property bindings like this:
+
+`.name=${'Steven'}`
+
+For boolean properties, use a question mark instead of a period, like this:
+
+`?programmer=${true}`.
+
+You generally want to expose your component's external attribute and property
+API with @property() instead of
+@state()static properties = {propName: {state: false}}.
+
+{% playground-ide "articles/lit-cheat-sheet/pass-data-down", true %}
+
+**Related Documentation & Topics:**
+
+- [Expressions](/docs/templates/expressions/)
+- [Expressions – Attribute Expressions](/docs/templates/expressions/#attribute-expressions)
+- [Expressions – Property Expressions](/docs/templates/expressions/#property-expressions)
+- [Event communication between web components](https://www.youtube.com/watch?v=T9mxtnoy9Qw&themeRefresh=1) (video)
+
+### Dispatch Events Up
+
+To send data up the tree to ancestors, you can dispatch custom events. To emit
+an event, use `Element.dispatchEvent()`.
+
+`dispatchEvent()` takes an event object as the first argument. Construct a
+custom event object like this:
+
+`new CustomEvent('event-name', {detail: data, bubbles: true, composed: true})`
+
+Provide data you want to pass to ancestors in the `detail` property of the
+event, and ancestors can react to the event by adding an event listener to the
+component like this:
+
+`@event-name=${this.eventHandler}`
+
+If you want an event to bubble through shadow Roots, set `composed: true`.
+
+{% playground-ide "articles/lit-cheat-sheet/data-up", true %}
+
+**Related Documentation & Topics:**
+
+- [Event communication between web components](https://www.youtube.com/watch?v=T9mxtnoy9Qw&themeRefresh=1) (video)
+- [A complete guide on shadow DOM and event propagation](https://pm.dartus.fr/posts/2021/shadow-dom-and-event-propagation/) (external)
+- [Expressions](/docs/templates/expressions/)
+- [Expressions – Event listener expressions](/docs/templates/expressions/#event-listener-expressions)
+- [Events](/docs/components/events/)
+- [Customizing event listener options](/docs/components/events/#event-options-decorator)
+
+## Accessing DOM
+
+### Getting An Element Reference
+
+The `@query` decorator allows you to access a reference to a single element in
+the component's shadow DOM using the syntax of
+`ShadowRoot.prototype.querySelector()`.
+
+In JavaScript, you can access the element using
+`this.shadowRoot.querySelector()`.
+
+{% playground-ide "articles/lit-cheat-sheet/dom-query", true %}
+
+{% aside "warn" %}
+
+NOTE: DOM is typically not ready until `firstUpdated` is called.
+
+This means that DOM is accessible by `updated()` on first render as well, but
+not in `constructor()`, `connectedCallback()`, or `willUpdate()` until
+subsequent renders.
+
+{% endaside %}
+
+**Related Documentation & Topics:**
+
+- [@query](/docs/components/shadow-dom/#query)
+- [@queryAll](/docs/components/shadow-dom/#query-all)
+- [@queryAsync](/docs/components/shadow-dom/#query-async)
+- [Lit lifecycle](/docs/components/lifecycle/)
+
+### Other Methods to Get An Element Reference
+
+The `ref()` directive is a lit-html-specific method to acquire an element
+reference. The `ref()` directive is a good alternative when:
+
+- You can't use the `@query` decorator (or its JS equivalent)
+- You cannot determine when an element will be rendered
+- You need to pass element references from a child to a parent component (not common)
+- You're migrating from another framework like React
+
+{% playground-ide "articles/lit-cheat-sheet/dom-ref", true %}
+
+{% aside "positive" %}
+
+The `ref()` directive also accepts a callback function that will be called with
+the element reference as an argument when the target element is connected to the
+DOM.
+
+Though, it is generally recommended to use the `@query` or the
+`@queryAsync` decorators when possible as they are generally more performant and
+less-reliant on Lit.
+
+{% endaside %}
+
+**Related Documentation & Topics:**
+
+- [Built-in Directives – ref](/docs/templates/directives/#referencing-rendered-dom)
+- [Templates – Element Expressions](/docs/templates/expressions/#element-expressions)
+
+### Getting An Element Reference After Update
+
+The `@queryAsync` decorator is just like the `@query` decorator, but it waits
+for the current host element to finish its update before resolving. This is
+useful when you need to access an element that is rendered asynchronously by a
+change in the component's state.
+
+The JavaScript equivalent awaits the `this.updateComplete` promise before
+calling `this.shadowRoot.querySelector()`.
+
+{% playground-ide "articles/lit-cheat-sheet/dom-query-async", true %}
+
+**Related Documentation & Topics:**
+
+- [@queryAsync](/docs/components/shadow-dom/#query-async)
+- [`updateComplete`](/docs/components/lifecycle/#updatecomplete)
+- [`getUpdateComplete()`](/docs/components/lifecycle/#getUpdateComplete)
+- [Lit lifecycle](/docs/components/lifecycle/)
+- [@query](/docs/components/shadow-dom/#query)
+- [@queryAll](/docs/components/shadow-dom/#query-all)
+- [Waiting for an update](#waiting-for-an-update)
+
+### Accessing Slotted Content
+
+Shadow DOM uses the `` element which allows you to project content from
+outside the shadow root into the shadow root. You can access slotted content
+using the @queryAssignedElements decoratorHTMLSlotElement.assignedElements() method.
+
+You give it a slot name to access, and the element selector to filter for.
+
+{% playground-ide "articles/lit-cheat-sheet/dom-qae", true %}
+
+**Related Documentation & Topics:**
+
+- [Accessing Slotted Children](/docs/components/shadow-dom/#accessing-slotted-children)
+- [Slots](/docs/components/shadow-dom/#slots)
+- [`assignedElements` - MDN](https://developer.mozilla.org/en-US/docs/Web/API/HTMLSlotElement/assignedElements) (external)
+- [`HTMLSlotElement` - MDN](https://developer.mozilla.org/en-US/docs/Web/API/HTMLSlotElement) (external)
+- [`` - MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/slot) (external)
+
+## Signals
+
+Signals are data structures for managing observable state. They either store a
+value or compute a value based on other signals. The Lit project tries to
+conform to the
+[Signals standard proposal](https://github.com/tc39/proposal-signals) via the
+[`@lit-labs/signals` package](https://www.npmjs.com/package/@lit-labs/signals)
+in order to provide a cross-framework standard for reactive state management
+solution.
+
+### Common Signal Setup (SignalWatcher)
+
+The most common way to use signals in Lit is to use the `SignalWatcher` mixin.
+When an accessed signal value changes, `SignalWatcher` will trigger the Lit
+element update lifecycle. This includes signals read in `shouldUpdate()`,
+`willUpdate()`, `update()`, `render()`, `updated()`, `firstUpdated()`, and
+reactive controller's `hostUpdate()` and `hostUpdated()`.
+
+{% playground-ide "articles/lit-cheat-sheet/signal-watcher", true %}
+
+**Related Documentation & Topics:**
+
+- [Signals docs](/docs/data/signals/)
+- [Lit lifecycle](/docs/components/lifecycle/)
+- [`@lit-labs/signals` npm package](https://www.npmjs.com/package/@lit-labs/signals) (external)
+- [Signals standards proposal](https://github.com/tc39/proposal-signals) (external)
+
+### Pinpoint Signal Updates (watch directive)
+
+The `watch()` directive allows you to pinpoint exactly where a signal should
+update the DOM without re-triggering the lit-html re-render. What this means is
+that using the `watch()` directive will not trigger the `render()` unless it
+triggers the change of a traditional Lit Reactive Property.
+
+{% playground-ide "articles/lit-cheat-sheet/signals-watch-directive", true %}
+
+{% aside "warn" "no-header" %}
+
+This may be a helpful way to optimize performance in your Lit components, but
+*always measure for your use case*.
+
+{% endaside %}
+
+**Related Documentation & Topics:**
+
+- [The `watch()` directive](https://www.npmjs.com/package/@lit-labs/signals#watch-directive) (external)
+- [Reactive Properties](#reactive-properties)
+- [Signals docs](/docs/data/signals/)
+- [Lit lifecycle](/docs/components/lifecycle/)
+- [`@lit-labs/signals` npm package](https://www.npmjs.com/package/@lit-labs/signals) (external)
+- [Signals standards proposal](https://github.com/tc39/proposal-signals) (external)
+
+### Signals HTML Template Tag
+
+The `@lit-labs/signals` package also provides an `html` template tag that can
+be used in place of Lit's default `html` tag and automatically wraps any signals
+in the template with a `watch()` directive.
+
+{% playground-ide "articles/lit-cheat-sheet/signals-html-tag", true %}
+
+**Related Documentation & Topics:**
+
+- [The `html` tag and `withWatch()`](https://www.npmjs.com/package/@lit-labs/signals#html-tag-and-withwatch) (external)
+- [the `SignalWatcher()` mixin](#common-signal-setup-(signalwatcher))
+- [The `watch()` directive](#pinpoint-signal-updates-(watch-directive))
+
+### Make Signal Values from Other Signals (computed)
+
+Sometimes you need to derive a value from other signals. You can do this with
+a `computed()` signal.
+
+{% playground-ide "articles/lit-cheat-sheet/signals-computed", true %}
+
+**Related Documentation & Topics:**
+
+- [Signals docs](/docs/data/signals/)
+- [`@lit-labs/signals` npm package](https://www.npmjs.com/package/@lit-labs/signals) (external)
+
+### Reacting to Signal Changes (effects)
+
+The `@lit-labs/signals` package currently does not provide any signal-specific
+effects, but signal changes used in `render()` or the Lit lifecycle will trigger
+the Lit lifecycle like any other Reactive Property. You can trigger effects in
+the Lit lifecycle by using signals in the same way you would use Reactive
+Properties.
+
+{% playground-ide "articles/lit-cheat-sheet/signals-effect", true %}
+
+{% aside "warn" %}
+
+Effects implemented in this way are not compatible with the `watch()` directive.
+
+Because we're calling `State.prototype.get()` in the Lit lifecycle, the
+`SignalWatcher` mixin will counteract the pinpoint update behavior of that the
+`watch()` directive and trigger a full re-render.
+
+{% endaside %}
+
+{% aside "info" "no-header" %}
+
+To create a signal effect outside a Lit component, the signal-polyfill package
+provides a
+[simple, example implementation](https://github.com/proposal-signals/signal-polyfill?tab=readme-ov-file#creating-a-simple-effect)
+of an `effect()` compatible with Lit's signal implementation.
+
+{% endaside %}
+
+**Related Documentation & Topics:**
+
+- [Signals docs](/docs/data/signals/)
+- [`@lit-labs/signals` npm package](https://www.npmjs.com/package/@lit-labs/signals) (external)
+- [The `changedProperties` map](/docs/components/lifecycle/#changed-properties)
+- [signal-polyfill – Creating a simple effect](https://github.com/proposal-signals/signal-polyfill?tab=readme-ov-file#creating-a-simple-effect) (external)
+
+### Sharing Global, Reactive Data Across Components
+
+If your component needs to share a global state with another component and you
+do not need your component to be compatible with Lit's declarative event
+listener syntax, you can use a shared signal to share state across components.
+
+Here is the scoreboard example from the
+[Dispatch Events Up](#dispatch-events-up) section, but using shared signals.
+
+{% playground-ide "articles/lit-cheat-sheet/signals-share", true %}
+
+**Related Documentation & Topics:**
+
+- [Signals docs](/docs/data/signals/)
+- [`@lit-labs/signals` npm package](https://www.npmjs.com/package/@lit-labs/signals) (external)
+- [Data Flow and State Management](#data-flow-and-state-management)
diff --git a/packages/lit-dev-content/site/css/articles.css b/packages/lit-dev-content/site/css/articles.css
index 125fcd0ff..6ece73108 100644
--- a/packages/lit-dev-content/site/css/articles.css
+++ b/packages/lit-dev-content/site/css/articles.css
@@ -9,6 +9,13 @@ article {
padding-block-start: 0;
}
+/*
+ this is not visible in articles, but the scrollbar is on FF.
+*/
+#docsNavWrapper {
+ overflow-y: auto;
+}
+
/* ------------------------------------
* Header Section
* ------------------------------------ */
diff --git a/packages/lit-dev-content/site/css/docs.css b/packages/lit-dev-content/site/css/docs.css
index e72209812..dc94c35bc 100644
--- a/packages/lit-dev-content/site/css/docs.css
+++ b/packages/lit-dev-content/site/css/docs.css
@@ -137,11 +137,11 @@ litdev-example[filename] {
}
body[code-language-preference="ts"] litdev-example {
- --litdev-example-editor-height: calc(var(--litdev-example-editor-lines-ts) * 22px + 22px);
+ --litdev-example-editor-height: calc(var(--litdev-example-editor-lines-ts) * 22.4px + 22px);
}
body[code-language-preference="js"] litdev-example {
- --litdev-example-editor-height: calc(var(--litdev-example-editor-lines-js) * 22px + 22px);
+ --litdev-example-editor-height: calc(var(--litdev-example-editor-lines-js) * 22.4px + 22px);
}
litdev-example {
diff --git a/packages/lit-dev-content/site/docs/v3/templates/expressions.md b/packages/lit-dev-content/site/docs/v3/templates/expressions.md
index 474b918dc..5896b1b7b 100644
--- a/packages/lit-dev-content/site/docs/v3/templates/expressions.md
+++ b/packages/lit-dev-content/site/docs/v3/templates/expressions.md
@@ -245,7 +245,7 @@ html`
This text may be hidden.
`;
### Removing an attribute { #removing-attribute }
-Sometimes you want to set an attribute only under certain conditions, and otherwise remove the attribute. For common "boolean attributes" like `disabled` and `hidden` where you want to set the attribute to an empty string for a truthy value and remove it otherwise, use a [boolean attribute](#boolean-attribute-expressions). Sometimes, however, you might require a different condition for adding or removing an attribute.
+Sometimes you want to set an attribute only under certain conditions, and otherwise remove the attribute. For common "boolean attributes" like `disabled` and `hidden` where you want to set the attribute to an empty string for a truthy value and remove it otherwise, use a [boolean attribute](#boolean-attribute-expressions). Sometimes, however, you might require a different condition for adding or removing an attribute.
For example, consider:
@@ -570,6 +570,7 @@ class MyButton extends LitElement {
// These strings MUST be trusted, otherwise this is an XSS vulnerability
const tag = getTagName();
const activeAttribute = getActiveAttribute();
+ // html should be imported from `lit/static-html.js`
return html`
<${unsafeStatic(tag)} ${unsafeStatic(activeAttribute)}=${this.active}>
${this.caption}
@@ -595,6 +596,7 @@ class MyButton extends LitElement {
// These strings MUST be trusted, otherwise this is an XSS vulnerability
const tag = getTagName();
const activeAttribute = getActiveAttribute();
+ // html should be imported from `lit/static-html.js`
return html`
<${unsafeStatic(tag)} ${unsafeStatic(activeAttribute)}=${this.active}>
${this.caption}
diff --git a/packages/lit-dev-content/site/images/articles/lit_cheat_sheet.jpg b/packages/lit-dev-content/site/images/articles/lit_cheat_sheet.jpg
new file mode 100644
index 000000000..432687ec1
Binary files /dev/null and b/packages/lit-dev-content/site/images/articles/lit_cheat_sheet.jpg differ
diff --git a/packages/lit-dev-content/site/images/articles/lit_cheat_sheet_2x.jpg b/packages/lit-dev-content/site/images/articles/lit_cheat_sheet_2x.jpg
new file mode 100644
index 000000000..e8d88d89d
Binary files /dev/null and b/packages/lit-dev-content/site/images/articles/lit_cheat_sheet_2x.jpg differ
diff --git a/packages/lit-dev-content/src/components/litdev-example.ts b/packages/lit-dev-content/src/components/litdev-example.ts
index 705d58556..6dbc2d222 100644
--- a/packages/lit-dev-content/src/components/litdev-example.ts
+++ b/packages/lit-dev-content/src/components/litdev-example.ts
@@ -5,7 +5,7 @@
*/
import {LitElement, html, css, nothing} from 'lit';
-import {property} from 'lit/decorators.js';
+import {property, state} from 'lit/decorators.js';
import {styleMap} from 'lit/directives/style-map.js';
import {ifDefined} from 'lit/directives/if-defined.js';
import {
@@ -110,6 +110,23 @@ export class LitDevExample extends LitElement {
@property()
project?: string;
+ /**
+ * Whether to load the project on intersection with the viewport.
+ */
+ @property({type: Boolean})
+ lazy = false;
+
+ /**
+ * Whether or not to load the project. Setting Lazy to true will re-initialize
+ * this value to `false`. We would want to do this so that we do not load the
+ * large TS worker file until the user has scrolled to the example. This is
+ * something we would want to do if we want to have the server cache the
+ * worker. Otherwise every example on the page would load the worker at the
+ * same time and none of them would be cached.
+ */
+ @state()
+ private loadProject = true;
+
/**
* Name of file in project to display.
* If no file is provided, we show the tab-bar with all project files.
@@ -143,6 +160,12 @@ export class LitDevExample extends LitElement {
this.requestUpdate();
};
+ willUpdate() {
+ if (!this.hasUpdated) {
+ this.loadProject = !this.lazy;
+ }
+ }
+
render() {
if (!this.project) {
return nothing;
@@ -162,17 +185,23 @@ export class LitDevExample extends LitElement {
mode === 'ts' ? this.filename : this.filename?.replace(/.ts$/, '.js');
return html`
-
-
+ ${this.loadProject
+ ? // We need to conditionally render this because playground-project
+ // will load its serviceworker on firstUpdated
+ html`
+ `
+ : nothing}
${showTabBar
? html``
: nothing}
@@ -184,15 +213,35 @@ export class LitDevExample extends LitElement {
-
+
`;
}
+
+ firstUpdated() {
+ if (this.lazy) {
+ const io = new IntersectionObserver(
+ (entries) => {
+ if (entries[0].isIntersecting) {
+ this.loadProject = true;
+ io.disconnect();
+ }
+ },
+ {
+ rootMargin: '40px',
+ }
+ );
+
+ io.observe(this);
+ }
+ }
}
customElements.define('litdev-example', LitDevExample);
diff --git a/packages/lit-dev-server/src/server.ts b/packages/lit-dev-server/src/server.ts
index c3b71cf67..23da80b89 100644
--- a/packages/lit-dev-server/src/server.ts
+++ b/packages/lit-dev-server/src/server.ts
@@ -92,6 +92,16 @@ app.use(
if (path.includes('/fonts/')) {
res.setHeader('Cache-Control', 'max-age=31536000');
}
+ if (path.includes('/playground-typescript-worker.js')) {
+ // This is a huge file, so we want to cache the request for 2 minutes
+ // which should basically handle a page with multiple playgrounds.
+ // Then after those two minutes, it will use the same cached file for a
+ // day while it revalidates the cache in the background.
+ res.setHeader(
+ 'Cache-Control',
+ 'max-age=120, stale-while-revalidate=86400'
+ );
+ }
},
})
);
diff --git a/packages/lit-dev-tests/src/playwright/learn.spec.js-snapshots/learnCatalog-dark-darwin.png b/packages/lit-dev-tests/src/playwright/learn.spec.js-snapshots/learnCatalog-dark-darwin.png
index 90cd3065c..92f4d767b 100644
Binary files a/packages/lit-dev-tests/src/playwright/learn.spec.js-snapshots/learnCatalog-dark-darwin.png and b/packages/lit-dev-tests/src/playwright/learn.spec.js-snapshots/learnCatalog-dark-darwin.png differ
diff --git a/packages/lit-dev-tests/src/playwright/learn.spec.js-snapshots/learnCatalog-darwin.png b/packages/lit-dev-tests/src/playwright/learn.spec.js-snapshots/learnCatalog-darwin.png
index 8f5c8fd9a..cb7b524a7 100644
Binary files a/packages/lit-dev-tests/src/playwright/learn.spec.js-snapshots/learnCatalog-darwin.png and b/packages/lit-dev-tests/src/playwright/learn.spec.js-snapshots/learnCatalog-darwin.png differ
diff --git a/packages/lit-dev-tools-cjs/src/playground-plugin/plugin.ts b/packages/lit-dev-tools-cjs/src/playground-plugin/plugin.ts
index e9fa8c5c9..a03f1341f 100644
--- a/packages/lit-dev-tools-cjs/src/playground-plugin/plugin.ts
+++ b/packages/lit-dev-tools-cjs/src/playground-plugin/plugin.ts
@@ -163,31 +163,37 @@ export const playgroundPlugin = (
(code: string, lang: 'js' | 'ts' | 'html' | 'css') => render(code, lang)
);
- eleventyConfig.addShortcode('playground-ide', async (project: string) => {
- if (!project) {
- throw new Error(
- `Invalid playground-ide invocation.` +
- `Usage {% playground-ide "path/to/project" %}`
+ eleventyConfig.addShortcode(
+ 'playground-ide',
+ async (project: string, lazy = false) => {
+ if (!project) {
+ throw new Error(
+ `Invalid playground-ide invocation.` +
+ `Usage {% playground-ide "path/to/project" %}`
+ );
+ }
+ project = trimTrailingSlash(project);
+ const config = await readProjectConfig(project);
+ const firstFilename = Object.keys(config.files ?? {})[0];
+ const numVisibleLines = await getNumVisibleLinesForProjectFile(
+ project,
+ firstFilename
);
- }
- project = trimTrailingSlash(project);
- const config = await readProjectConfig(project);
- const firstFilename = Object.keys(config.files ?? {})[0];
- const numVisibleLines = await getNumVisibleLinesForProjectFile(
- project,
- firstFilename
- );
- const previewHeight = config.previewHeight ?? '120px';
- return `
- " character on the
+ // line right after the last attribute or else markdown will not render
+ // the closing tag correctly because it will be in a `