From bf827fb41044ad9ad5cf94f25fa5dbd54451d258 Mon Sep 17 00:00:00 2001 From: Sergey Chernyshev Date: Sat, 25 Apr 2026 20:12:09 -0400 Subject: [PATCH 01/13] Add Convert on Arrival pattern Closes #2 Co-Authored-By: Claude Opus 4.7 (1M context) --- src/assets/convert_on_arrival_thumbnail.svg | 29 ++++++++++ src/patterns/convert_on_arrival.md | 62 +++++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 src/assets/convert_on_arrival_thumbnail.svg create mode 100644 src/patterns/convert_on_arrival.md diff --git a/src/assets/convert_on_arrival_thumbnail.svg b/src/assets/convert_on_arrival_thumbnail.svg new file mode 100644 index 0000000..201f2a7 --- /dev/null +++ b/src/assets/convert_on_arrival_thumbnail.svg @@ -0,0 +1,29 @@ + + + Static first, interactive later + + + + Slide 1 + + + + + + + + + + + 1 / 5 + + + + + + + + + + + diff --git a/src/patterns/convert_on_arrival.md b/src/patterns/convert_on_arrival.md new file mode 100644 index 0000000..a754467 --- /dev/null +++ b/src/patterns/convert_on_arrival.md @@ -0,0 +1,62 @@ +--- +layout: article.njk +title: Convert on Arrival +tags: pattern +thumbnail: /assets/convert_on_arrival_thumbnail.svg +og_image: /assets/speed_patterns_og_image.jpg +order: 5 +--- + +Rich interactive components — carousels, tabbed panels, video players, maps, comparison sliders — are valuable, but they bring along a lot of code and data. Waiting for all of it to load before showing anything leaves the user staring at a blank container. + + + +## The Problem + +A typical "above the fold" carousel illustrates the issue well. To work, it needs: + +- Multiple images (or video frames) +- JavaScript for navigation, touch handling and autoplay +- CSS for layout and transitions +- Often, font and analytics dependencies + +Until all of that arrives, the user either sees nothing, sees a spinner, or sees a janky half-loaded version that shifts as code initializes. Yet 90% of the visible value of a carousel is its first slide — the one the user sees on arrival. + +## Solution + +Render the simplest possible static representation of the component immediately, using only HTML and CSS. Then progressively enhance it into the full interactive version as the supporting code and data arrive. + +For a carousel: + +1. Render the **first slide** as a plain image (or HTML block) at the correct dimensions. +2. Once the carousel JavaScript and remaining slides are downloaded, **convert** the static element into the full interactive carousel — without any layout shift. +3. The transition from static to interactive should be visually invisible to the user. + +The same pattern applies broadly: + +- **Video players** — show the poster image with a play button immediately; load the player on click or when idle. +- **Maps** — show a static map tile or screenshot first; swap for an interactive map when the library loads. +- **Tabbed panels** — render the active tab's content as plain HTML; attach tab-switching behavior later. +- **Tables with sort/filter** — render the rows server-side; attach client-side controls progressively. + +## Why This Works + +The dominant use of an interactive component is often passive consumption of its default state. By optimizing for that default state, you get: + +- **Faster first paint** — no JavaScript blocks the initial render. +- **Faster Largest Contentful Paint (LCP)** — the hero image arrives in the first HTML response. +- **No layout shift** — sizes are fixed in the static markup before any code runs. +- **Resilience** — if scripts fail or are slow, the user still sees usable content. + +## Guidelines + +- **Match dimensions exactly.** The static placeholder must occupy the same space as the final component to avoid [layout shift](/patterns/immutable_layout/). +- **Make the static state useful, not decorative.** It should be the real first slide, the real hero image, the real default tab — not a generic placeholder. +- **Defer the upgrade.** Use `requestIdleCallback`, `IntersectionObserver`, or interaction-based loading so you don't block the main thread fighting for the same resources as the rest of the page. +- **Test the static-only experience.** Disable JavaScript and confirm the page is still presentable and on-brand. + +## Related Patterns + +- [Fast Start](/patterns/fast_start/) — getting that first paint up quickly is what makes this pattern pay off. +- [Immutable Layout](/patterns/immutable_layout/) — the upgrade must not shift the page. +- [Skeletal Designs](/patterns/skeletal_designs/) — for parts of the UI where no useful static representation exists. From 0125228bd414ed7eaf59779969f1b511573babfd Mon Sep 17 00:00:00 2001 From: Sergey Chernyshev Date: Sun, 26 Apr 2026 12:25:31 -0400 Subject: [PATCH 02/13] Move technical implementation details to the end of the article --- src/patterns/convert_on_arrival.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/patterns/convert_on_arrival.md b/src/patterns/convert_on_arrival.md index a754467..69b6da8 100644 --- a/src/patterns/convert_on_arrival.md +++ b/src/patterns/convert_on_arrival.md @@ -52,7 +52,7 @@ The dominant use of an interactive component is often passive consumption of its - **Match dimensions exactly.** The static placeholder must occupy the same space as the final component to avoid [layout shift](/patterns/immutable_layout/). - **Make the static state useful, not decorative.** It should be the real first slide, the real hero image, the real default tab — not a generic placeholder. -- **Defer the upgrade.** Use `requestIdleCallback`, `IntersectionObserver`, or interaction-based loading so you don't block the main thread fighting for the same resources as the rest of the page. +- **Don't compete with the critical path.** The upgrade should wait until the rest of the page is settled, so it doesn't fight for resources during the most important part of load. - **Test the static-only experience.** Disable JavaScript and confirm the page is still presentable and on-brand. ## Related Patterns @@ -60,3 +60,14 @@ The dominant use of an interactive component is often passive consumption of its - [Fast Start](/patterns/fast_start/) — getting that first paint up quickly is what makes this pattern pay off. - [Immutable Layout](/patterns/immutable_layout/) — the upgrade must not shift the page. - [Skeletal Designs](/patterns/skeletal_designs/) — for parts of the UI where no useful static representation exists. + +## Technical Implementation + +Defer the upgrade until the page is no longer fighting for resources. Useful primitives: + +- `requestIdleCallback` — schedule the upgrade for an idle slice of the main thread. +- `IntersectionObserver` — only upgrade components that are actually visible (or about to be). +- Interaction-based loading — wait until the user hovers, focuses, or clicks the static placeholder before downloading the heavier interactive code. +- Dynamic `import()` — code-split the interactive implementation so it isn't part of the critical bundle. + +The static placeholder should be authored as plain HTML at the correct final dimensions; the upgrade script then mounts the interactive version into (or in place of) that container without changing its size. From da8c051595e4839153cc89ec11ea5b346800cabc Mon Sep 17 00:00:00 2001 From: Sergey Chernyshev Date: Sun, 26 Apr 2026 12:35:23 -0400 Subject: [PATCH 03/13] Drop trailing periods from list items --- src/patterns/convert_on_arrival.md | 44 +++++++++++++++--------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/patterns/convert_on_arrival.md b/src/patterns/convert_on_arrival.md index 69b6da8..dfbdfe4 100644 --- a/src/patterns/convert_on_arrival.md +++ b/src/patterns/convert_on_arrival.md @@ -28,46 +28,46 @@ Render the simplest possible static representation of the component immediately, For a carousel: -1. Render the **first slide** as a plain image (or HTML block) at the correct dimensions. -2. Once the carousel JavaScript and remaining slides are downloaded, **convert** the static element into the full interactive carousel — without any layout shift. -3. The transition from static to interactive should be visually invisible to the user. +1. Render the **first slide** as a plain image (or HTML block) at the correct dimensions +2. Once the carousel JavaScript and remaining slides are downloaded, **convert** the static element into the full interactive carousel — without any layout shift +3. The transition from static to interactive should be visually invisible to the user The same pattern applies broadly: -- **Video players** — show the poster image with a play button immediately; load the player on click or when idle. -- **Maps** — show a static map tile or screenshot first; swap for an interactive map when the library loads. -- **Tabbed panels** — render the active tab's content as plain HTML; attach tab-switching behavior later. -- **Tables with sort/filter** — render the rows server-side; attach client-side controls progressively. +- **Video players** — show the poster image with a play button immediately; load the player on click or when idle +- **Maps** — show a static map tile or screenshot first; swap for an interactive map when the library loads +- **Tabbed panels** — render the active tab's content as plain HTML; attach tab-switching behavior later +- **Tables with sort/filter** — render the rows server-side; attach client-side controls progressively ## Why This Works The dominant use of an interactive component is often passive consumption of its default state. By optimizing for that default state, you get: -- **Faster first paint** — no JavaScript blocks the initial render. -- **Faster Largest Contentful Paint (LCP)** — the hero image arrives in the first HTML response. -- **No layout shift** — sizes are fixed in the static markup before any code runs. -- **Resilience** — if scripts fail or are slow, the user still sees usable content. +- **Faster first paint** — no JavaScript blocks the initial render +- **Faster Largest Contentful Paint (LCP)** — the hero image arrives in the first HTML response +- **No layout shift** — sizes are fixed in the static markup before any code runs +- **Resilience** — if scripts fail or are slow, the user still sees usable content ## Guidelines -- **Match dimensions exactly.** The static placeholder must occupy the same space as the final component to avoid [layout shift](/patterns/immutable_layout/). -- **Make the static state useful, not decorative.** It should be the real first slide, the real hero image, the real default tab — not a generic placeholder. -- **Don't compete with the critical path.** The upgrade should wait until the rest of the page is settled, so it doesn't fight for resources during the most important part of load. -- **Test the static-only experience.** Disable JavaScript and confirm the page is still presentable and on-brand. +- **Match dimensions exactly.** The static placeholder must occupy the same space as the final component to avoid [layout shift](/patterns/immutable_layout/) +- **Make the static state useful, not decorative.** It should be the real first slide, the real hero image, the real default tab — not a generic placeholder +- **Don't compete with the critical path.** The upgrade should wait until the rest of the page is settled, so it doesn't fight for resources during the most important part of load +- **Test the static-only experience.** Disable JavaScript and confirm the page is still presentable and on-brand ## Related Patterns -- [Fast Start](/patterns/fast_start/) — getting that first paint up quickly is what makes this pattern pay off. -- [Immutable Layout](/patterns/immutable_layout/) — the upgrade must not shift the page. -- [Skeletal Designs](/patterns/skeletal_designs/) — for parts of the UI where no useful static representation exists. +- [Fast Start](/patterns/fast_start/) — getting that first paint up quickly is what makes this pattern pay off +- [Immutable Layout](/patterns/immutable_layout/) — the upgrade must not shift the page +- [Skeletal Designs](/patterns/skeletal_designs/) — for parts of the UI where no useful static representation exists ## Technical Implementation Defer the upgrade until the page is no longer fighting for resources. Useful primitives: -- `requestIdleCallback` — schedule the upgrade for an idle slice of the main thread. -- `IntersectionObserver` — only upgrade components that are actually visible (or about to be). -- Interaction-based loading — wait until the user hovers, focuses, or clicks the static placeholder before downloading the heavier interactive code. -- Dynamic `import()` — code-split the interactive implementation so it isn't part of the critical bundle. +- `requestIdleCallback` — schedule the upgrade for an idle slice of the main thread +- `IntersectionObserver` — only upgrade components that are actually visible (or about to be) +- Interaction-based loading — wait until the user hovers, focuses, or clicks the static placeholder before downloading the heavier interactive code +- Dynamic `import()` — code-split the interactive implementation so it isn't part of the critical bundle The static placeholder should be authored as plain HTML at the correct final dimensions; the upgrade script then mounts the interactive version into (or in place of) that container without changing its size. From ae8472768c47da482bcb9a16e136a1337122b44b Mon Sep 17 00:00:00 2001 From: Sergey Chernyshev Date: Sun, 26 Apr 2026 13:11:13 -0400 Subject: [PATCH 04/13] Redraw thumbnail to follow canonical diagram theme --- src/assets/convert_on_arrival_thumbnail.svg | 50 +++++++++++---------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/src/assets/convert_on_arrival_thumbnail.svg b/src/assets/convert_on_arrival_thumbnail.svg index 201f2a7..7458bbf 100644 --- a/src/assets/convert_on_arrival_thumbnail.svg +++ b/src/assets/convert_on_arrival_thumbnail.svg @@ -1,29 +1,31 @@ - - Static first, interactive later - - - - Slide 1 - - + + Static first, interactive later + + + + Slide 1 + + + static (HTML only) - - - + + - - - - 1 / 5 - - - - - - - - - + + + + + 1 / 5 + + + + + + + + + + interactive (after JS) From a8f0ff7a6795ad8fcc2f6a95223ecaceca5d17a0 Mon Sep 17 00:00:00 2001 From: Sergey Chernyshev Date: Sun, 26 Apr 2026 13:14:28 -0400 Subject: [PATCH 05/13] Embed thumbnail illustration in Solution section --- src/patterns/convert_on_arrival.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/patterns/convert_on_arrival.md b/src/patterns/convert_on_arrival.md index dfbdfe4..277c713 100644 --- a/src/patterns/convert_on_arrival.md +++ b/src/patterns/convert_on_arrival.md @@ -24,6 +24,11 @@ Until all of that arrives, the user either sees nothing, sees a spinner, or sees ## Solution +
+
The static placeholder ships in the first HTML response; the carousel "converts" into its full interactive form once the supporting code arrives, without changing size.
+Two carousel panels side by side: the left shows a single static slide labeled 'Slide 1', the right shows the same panel as a fully interactive carousel with arrows, position dots and a counter '1 / 5' +
+ Render the simplest possible static representation of the component immediately, using only HTML and CSS. Then progressively enhance it into the full interactive version as the supporting code and data arrive. For a carousel: From 69e3e25bcd6cd57585fad8fba4a7680ae3f11e22 Mon Sep 17 00:00:00 2001 From: Sergey Chernyshev Date: Sun, 26 Apr 2026 13:24:38 -0400 Subject: [PATCH 06/13] Move SVG title text to figcaption; description becomes paragraph --- src/assets/convert_on_arrival_thumbnail.svg | 3 +-- src/patterns/convert_on_arrival.md | 4 +++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/assets/convert_on_arrival_thumbnail.svg b/src/assets/convert_on_arrival_thumbnail.svg index 7458bbf..b724627 100644 --- a/src/assets/convert_on_arrival_thumbnail.svg +++ b/src/assets/convert_on_arrival_thumbnail.svg @@ -1,6 +1,5 @@ - + - Static first, interactive later diff --git a/src/patterns/convert_on_arrival.md b/src/patterns/convert_on_arrival.md index 277c713..e8ef4c5 100644 --- a/src/patterns/convert_on_arrival.md +++ b/src/patterns/convert_on_arrival.md @@ -24,8 +24,10 @@ Until all of that arrives, the user either sees nothing, sees a spinner, or sees ## Solution +The static placeholder ships in the first HTML response; the carousel "converts" into its full interactive form once the supporting code arrives, without changing size. +
-
The static placeholder ships in the first HTML response; the carousel "converts" into its full interactive form once the supporting code arrives, without changing size.
+
Static first, interactive later
Two carousel panels side by side: the left shows a single static slide labeled 'Slide 1', the right shows the same panel as a fully interactive carousel with arrows, position dots and a counter '1 / 5' From 9035fe9539541a242182848400508fc20005bd2a Mon Sep 17 00:00:00 2001 From: Sergey Chernyshev Date: Sun, 26 Apr 2026 13:35:19 -0400 Subject: [PATCH 07/13] Move figcaption below the image --- src/patterns/convert_on_arrival.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/patterns/convert_on_arrival.md b/src/patterns/convert_on_arrival.md index e8ef4c5..e2ffb32 100644 --- a/src/patterns/convert_on_arrival.md +++ b/src/patterns/convert_on_arrival.md @@ -27,8 +27,8 @@ Until all of that arrives, the user either sees nothing, sees a spinner, or sees The static placeholder ships in the first HTML response; the carousel "converts" into its full interactive form once the supporting code arrives, without changing size.
-
Static first, interactive later
Two carousel panels side by side: the left shows a single static slide labeled 'Slide 1', the right shows the same panel as a fully interactive carousel with arrows, position dots and a counter '1 / 5' +
Static first, interactive later
Render the simplest possible static representation of the component immediately, using only HTML and CSS. Then progressively enhance it into the full interactive version as the supporting code and data arrive. From e5a5fee493de00d2259a3e5e54866ac3890e62e7 Mon Sep 17 00:00:00 2001 From: Sergey Chernyshev Date: Sun, 26 Apr 2026 13:50:18 -0400 Subject: [PATCH 08/13] Shorten arrow so its tip clears the right panel --- src/assets/convert_on_arrival_thumbnail.svg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/assets/convert_on_arrival_thumbnail.svg b/src/assets/convert_on_arrival_thumbnail.svg index b724627..22493bd 100644 --- a/src/assets/convert_on_arrival_thumbnail.svg +++ b/src/assets/convert_on_arrival_thumbnail.svg @@ -9,9 +9,9 @@ static (HTML only) - + - + From 343a523c5b8f6af21e7ac041bad9e5e6c82d5dc2 Mon Sep 17 00:00:00 2001 From: Sergey Chernyshev Date: Sun, 26 Apr 2026 14:01:13 -0400 Subject: [PATCH 09/13] Use white-with-border content boxes (drop yellow/orange fills) --- src/assets/convert_on_arrival_thumbnail.svg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/assets/convert_on_arrival_thumbnail.svg b/src/assets/convert_on_arrival_thumbnail.svg index 22493bd..4952a35 100644 --- a/src/assets/convert_on_arrival_thumbnail.svg +++ b/src/assets/convert_on_arrival_thumbnail.svg @@ -1,7 +1,7 @@ - + Slide 1 @@ -13,7 +13,7 @@ - + 1 / 5 From 543ee37bcd16025ac6755eef887dbae1f458922b Mon Sep 17 00:00:00 2001 From: Sergey Chernyshev Date: Sun, 26 Apr 2026 14:08:59 -0400 Subject: [PATCH 10/13] Increase illustration size by 30% (400 -> 520) --- src/patterns/convert_on_arrival.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/patterns/convert_on_arrival.md b/src/patterns/convert_on_arrival.md index e2ffb32..e585881 100644 --- a/src/patterns/convert_on_arrival.md +++ b/src/patterns/convert_on_arrival.md @@ -27,7 +27,7 @@ Until all of that arrives, the user either sees nothing, sees a spinner, or sees The static placeholder ships in the first HTML response; the carousel "converts" into its full interactive form once the supporting code arrives, without changing size.
-
Two carousel panels side by side: the left shows a single static slide labeled 'Slide 1', the right shows the same panel as a fully interactive carousel with arrows, position dots and a counter '1 / 5' +Two carousel panels side by side: the left shows a single static slide labeled 'Slide 1', the right shows the same panel as a fully interactive carousel with arrows, position dots and a counter '1 / 5'
Static first, interactive later
From 01f5193c4d71df0c0041ecff2c168b936b5ecd9e Mon Sep 17 00:00:00 2001 From: Sergey Chernyshev Date: Sun, 26 Apr 2026 14:12:08 -0400 Subject: [PATCH 11/13] Match static-side text spacing to interactive side (no layout shift) --- src/assets/convert_on_arrival_thumbnail.svg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/assets/convert_on_arrival_thumbnail.svg b/src/assets/convert_on_arrival_thumbnail.svg index 4952a35..0b8d6d4 100644 --- a/src/assets/convert_on_arrival_thumbnail.svg +++ b/src/assets/convert_on_arrival_thumbnail.svg @@ -4,8 +4,8 @@ Slide 1 - - + + static (HTML only)
From 70df4d08e7198395888d12960a428a1c2a4ac4d1 Mon Sep 17 00:00:00 2001 From: Sergey Chernyshev Date: Sun, 26 Apr 2026 20:28:39 -0400 Subject: [PATCH 12/13] Add author and creation date to Convert on Arrival Author taken from the original issue (#2) creator; date is the issue creation date. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/patterns/convert_on_arrival.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/patterns/convert_on_arrival.md b/src/patterns/convert_on_arrival.md index e585881..84d3ab0 100644 --- a/src/patterns/convert_on_arrival.md +++ b/src/patterns/convert_on_arrival.md @@ -5,6 +5,10 @@ tags: pattern thumbnail: /assets/convert_on_arrival_thumbnail.svg og_image: /assets/speed_patterns_og_image.jpg order: 5 +date: 2017-12-04 +authors: + - name: Sergey Chernyshev + url: https://www.sergeychernyshev.com/ --- Rich interactive components — carousels, tabbed panels, video players, maps, comparison sliders — are valuable, but they bring along a lot of code and data. Waiting for all of it to load before showing anything leaves the user staring at a blank container. From 5855e2ade90111f2e4b9a9c0cef47360c91fff26 Mon Sep 17 00:00:00 2001 From: Sergey Chernyshev Date: Sun, 26 Apr 2026 20:41:19 -0400 Subject: [PATCH 13/13] Mark Convert on Arrival as AI-assisted Co-Authored-By: Claude Opus 4.7 (1M context) --- src/patterns/convert_on_arrival.md | 1 + 1 file changed, 1 insertion(+) diff --git a/src/patterns/convert_on_arrival.md b/src/patterns/convert_on_arrival.md index 84d3ab0..1f4a3d4 100644 --- a/src/patterns/convert_on_arrival.md +++ b/src/patterns/convert_on_arrival.md @@ -5,6 +5,7 @@ tags: pattern thumbnail: /assets/convert_on_arrival_thumbnail.svg og_image: /assets/speed_patterns_og_image.jpg order: 5 +ai_assisted: true date: 2017-12-04 authors: - name: Sergey Chernyshev