Repository: adobe/adobe-design-website
Pull request #66: feat: two-up layout
Author: login: arnest00
Labels: name: Ready for Dev Review
Created: 2025-05-06T06:04:00Z | Merged: 2025-05-08T20:19:45Z

## Summary of changes
- Link-List no longer takes a title and an optional link
- `.grid-item--50` takes up the entire grid on screen sizes below tablet size
- `decorateLayouts()` function supports two-up layout
- horizontal rule tags match `-NAME-` pattern

## Relevant Links
- Designs: [Figma](https://www.figma.com/design/QzvOszpLGT7fwSd6N7gaDW/Adobe-Design?node-id=841-5194&t=7bVdF3gMQ6LJqeNY-1) (on the homepage, an example of this layout is the Adobe Design News section, which contains a Link-List and Callout block)
- Story: [ADB-181](https://sparkbox.atlassian.net/browse/ADB-181)

## Test URLs:
- Before: https://main--adobe-design-website--adobe.aem.page/pattern-library/layouts
- After: https://feat-two-up-layout--adobe-design-website--adobe.aem.page/pattern-library/layouts

## Checklist
* [ ] This PR has visual changes, and has been reviewed by a designer.
* [ ] This PR has code changes, and our linters still pass.
* [ ] This PR has new code, so new tests were added or updated, and they pass.
* [ ] This PR affects production code, so it was browser tested (see below).
* [ ] The content of this PR requires documentation, so we added a detailed description of the component's purpose, requirements, quirks, and instructions for use by designers and developers. This includes accessibility information if pertinent.

## Validation
1. Visit the [Layout page on the branch preview](https://feat-two-up-layout--adobe-design-website--adobe.aem.page/pattern-library/layouts)
2. Scroll down to the Two-Up section
3. Verify that both Link-List and Button-Group blocks render as expected
4. Change the browser window size and confirm the layout is responsive
5. Visit the [Pattern Library page on the branch preview](https://feat-two-up-layout--adobe-design-website--adobe.aem.page/pattern-library/)
6. Verify the horizontal rule functionality still behaves as expected

---

## Browser Testing
We should aim to support the latest version of the listed browsers. For older versions or other browsers not on the list, content should be accessible, even if it doesn't completely match the designs.

Developers should test as they work in the browsers available on their machines. If they have access to other devices to test other browser/OS combinations, they should do that when possible.

**Windows**
* [ ] Firefox
* [ ] Chrome
* [ ] Edge

**MacOS**
* [x] Firefox
* [x] Chrome
* [ ] Safari
* [ ] Edge

**Android**
* [ ] Firefox
* [ ] Chrome
* [ ] Edge

**iOS**
* [ ] Safari

---



Diff:
diff --git a/blocks/cards/cards.test.js b/blocks/cards/cards.test.js
index ae9dc898..9c0ef57e 100644
--- a/blocks/cards/cards.test.js
+++ b/blocks/cards/cards.test.js
@@ -4,8 +4,8 @@ describe('Cards Block', () => {
   });
 
   it('should render the cards wrapper', async () => {
-    await page.waitForSelector('.cards');
-    const cardsWrapper = await page.$('.cards');
+    await page.waitForSelector('.card');
+    const cardsWrapper = await page.$('.card');
     expect(cardsWrapper).toExist();
   });
 });
diff --git a/blocks/link-list/link-list.js b/blocks/link-list/link-list.js
index a2d5b1d1..b9159d3f 100644
--- a/blocks/link-list/link-list.js
+++ b/blocks/link-list/link-list.js
@@ -1,14 +1,15 @@
-const buildLinkListAnchor = ({ textContent, url }) => {
-  const linkListItem = document.createElement('a');
-  linkListItem.className = 'link-list-item';
-  linkListItem.href = url;
+const buildLinkListItem = ({ textContent, url }) => {
+  const linkListItem = document.createElement('li');
+  const linkListItemAnchor = document.createElement('a');
+  linkListItemAnchor.className = 'link-list-item';
+  linkListItemAnchor.href = url;
 
   const linkListItemContent = document.createElement('div');
   linkListItemContent.className = 'link-list-item__content';
-  linkListItem.append(linkListItemContent);
+  linkListItemAnchor.append(linkListItemContent);
 
   if (!url.startsWith('https://adobe.design/')) {
-    linkListItem.classList.add('link-list-item--external');
+    linkListItemAnchor.classList.add('link-list-item--external');
   };
 
   textContent.forEach(textNode => {
@@ -23,36 +24,25 @@ const buildLinkListAnchor = ({ textContent, url }) => {
     linkListItemContent.children[2].className = 'link-list-item__job-location';
   };
 
+  linkListItem.append(linkListItemAnchor);
   return linkListItem;
 };
 
 export default async function decorate(block) {
+  const linkListContainer = document.createElement('div');
   const linkList = document.createElement('ul');
-
-  const linkListTitle = [...block.children][0].children[0].children[0];
-  linkListTitle.className = 'link-list__title';
-
-  let linkListFooterLink = null;
-  if ([...block.children][0].children[1].children.length === 1)
-    linkListFooterLink = [...block.children][0].children[1].children[0].children[0];
-
-  const linkListLinksData = [...block.children].slice(1).map(row => ({
+  linkListContainer.classList.add('link-list');
+  linkListContainer.append(linkList);
+  
+  const linkListLinksData = [...block.children].map(row => ({
     textContent: [...row.children[0].children],
     url: row.children[1].innerText,
   }));
-  linkListLinksData.forEach(row => {
-    const linkListItem = document.createElement('li');
-    const linkListAnchor = buildLinkListAnchor(row);
 
-    linkListItem.append(linkListAnchor);
+  linkListLinksData.forEach(row => {
+    const linkListItem = buildLinkListItem(row);
     linkList.append(linkListItem);
   });
 
-  block.innerHTML = '';
-  block.append(linkListTitle);
-  block.append(linkList);
-  if (linkListFooterLink) {
-    linkListFooterLink.classList.remove('button');
-    block.append(linkListFooterLink);
-  };
-}
+  block.replaceWith(linkListContainer);
+};
diff --git a/blocks/link-list/link-list.test.js b/blocks/link-list/link-list.test.js
index d28b34ac..33f1730d 100644
--- a/blocks/link-list/link-list.test.js
+++ b/blocks/link-list/link-list.test.js
@@ -9,21 +9,6 @@ describe('Link-List Block', () => {
     expect(linkLists.length).toBeGreaterThanOrEqual(1);
   });
 
-  it('should render the link-list title', async () => {
-    await page.waitForSelector('.link-list__title');
-    const linkListTitles = await page.$$('.link-list__title');
-    expect(linkListTitles.length).toBeGreaterThanOrEqual(1);
-  });
-
-  it('should render the link-list footer link, if it exists', async () => {
-    await page.waitForSelector('.link-list > a');
-    const linkListsWithFooterLink = await page.$$('.link-list > a');
-
-    if (linkListsWithFooterLink) {
-      expect(linkListsWithFooterLink.length).toBeGreaterThanOrEqual(1);
-    };
-  })
-
   describe('Link-List-Item', () => {
     beforeAll(async () => {
       await page.waitForSelector('.link-list-item');
diff --git a/scripts/aem.js b/scripts/aem.js
index 0da32558..1773a20b 100644
--- a/scripts/aem.js
+++ b/scripts/aem.js
@@ -362,17 +362,64 @@ function wrapTextNodes(block) {
 function decorateHorizontalRules(element) {
   element.querySelectorAll('p').forEach((p) => {
     const content = p.textContent.trim();
-    if (content === '<hr>' || content === '<hr fw>') {
+    if (content === '-hr-' || content === '-hr fw-') {
       const wrapper = document.createElement('div');
       wrapper.className = 'horizontal-rule-wrapper';
       const hr = document.createElement('hr');
-      hr.className = content === '<hr fw>' ? 'horizontal-rule horizontal-rule--full' : 'horizontal-rule';
+      hr.className = content === '-hr fw-' ? 'horizontal-rule horizontal-rule--full' : 'horizontal-rule';
       wrapper.appendChild(hr);
       p.parentElement.replaceWith(wrapper);
     }
   });
 }
 
+/**
+ * Converts '<layout>' text nodes into containers to allow for nesting of blocks within layouts
+ * @param {Element} element container element
+ */
+function decorateLayouts(element) {
+  // find all '<layout>' text nodes regardless of layout type
+  const layoutOpeningTags = Array.from(element.querySelectorAll('p'))
+    .filter((p) => p.textContent.trim().startsWith('-layout'));
+
+  for (const layoutOpeningTag of layoutOpeningTags) {
+    const layoutWrapper = layoutOpeningTag.parentNode;
+    let currentElement = layoutWrapper.nextElementSibling;
+    let layoutClosingTag = null;
+    const elementsToContain = [];
+
+    while (currentElement) {
+      if (currentElement.textContent.includes('-end layout-')) {
+        // ensure only the <p> containing the layout ender is removed at the end of this iteration
+        // there may be a layout starter in the same <div>
+        layoutClosingTag = Array.from(currentElement.querySelectorAll('p'))
+          .find((p) => p.textContent.trim() === '-end layout-');
+        break;
+      };
+
+      // TODO: support other layout types
+      // apply 50/50 layout for two-up
+      if (layoutOpeningTag.textContent.endsWith('two-up-'))
+        currentElement.classList.add('grid-item', 'grid-item--50');
+
+      elementsToContain.push(currentElement);
+      currentElement = currentElement.nextSibling;
+    };
+
+    // only creates a layout container for valid <layout></layout> pairs
+    if (layoutClosingTag) {
+      const layoutContainer = document.createElement('div');
+      layoutContainer.className = 'grid-container';
+
+      elementsToContain.forEach((e) => layoutContainer.appendChild(e));
+      layoutWrapper.after(layoutContainer);
+
+      layoutOpeningTag.remove();
+      layoutClosingTag.remove();
+    };
+  };
+};
+
 /**
  * Add <img> for icon, prefixed with codeBasePath and optional prefix.
  * @param {Element} [span] span element with icon classes
@@ -674,4 +721,5 @@ export {
   waitForFirstImage,
   wrapTextNodes,
   decorateHorizontalRules,
+  decorateLayouts,
 };
diff --git a/scripts/scripts.js b/scripts/scripts.js
index 04be5eb1..57f14035 100644
--- a/scripts/scripts.js
+++ b/scripts/scripts.js
@@ -3,6 +3,7 @@ import {
   loadHeader,
   loadFooter,
   decorateHorizontalRules,
+  decorateLayouts,
   decorateIcons,
   decorateSections,
   decorateBlocks,
@@ -61,10 +62,11 @@ function buildAutoBlocks(main) {
 // eslint-disable-next-line import/prefer-default-export
 export function decorateMain(main) {
   decorateHorizontalRules(main),
-  decorateIcons(main);
-  buildAutoBlocks(main);
-  decorateSections(main);
-  decorateBlocks(main);
+  decorateIcons(main),
+  buildAutoBlocks(main),
+  decorateSections(main),
+  decorateBlocks(main),
+  decorateLayouts(main);
 }
 
 /**
diff --git a/styles/styles.css b/styles/styles.css
index d5cd022a..31a22008 100644
--- a/styles/styles.css
+++ b/styles/styles.css
@@ -559,6 +559,10 @@ main .section.highlight {
 }
 
 /* ** Grid Layout Styles **  */
+main > .section > .grid-container {
+  margin-block: 4rem;
+}
+
 .grid-container {
   max-width: 100%;
 
@@ -572,14 +576,14 @@ main .section.highlight {
     @media (min-width: 22.5rem) {
       display: grid;
       grid-template-columns: repeat(8, [col] 1fr);
-      grid-column-gap: 0.5rem;
-      grid-row-gap: 0.5rem;
+      grid-column-gap: 4rem;
+      grid-row-gap: 4rem;
     }
 
     @media (min-width: 48rem) {
       grid-template-columns: repeat(12, [col] 1fr);
-      grid-column-gap: 1rem;
-      grid-row-gap: 1rem;
+      grid-column-gap: 4rem;
+      grid-row-gap: 4rem;
     }
   }
 }
@@ -587,12 +591,11 @@ main .section.highlight {
 .grid-item {
   min-height: 0;
   min-width: 0;
+  align-self: center;
 }
 
 .grid-item--66 {
-  @media (min-width: 22.5rem) {
-    grid-column: span 4;
-  }
+  grid-column: span 8;
 
   @media (min-width: 48rem) {
     grid-column: span 8;
@@ -600,9 +603,7 @@ main .section.highlight {
 }
 
 .grid-item--50 {
-  @media (min-width: 22.5rem) {
-    grid-column: span 4;
-  }
+  grid-column: span 8;
 
   @media (min-width: 48rem) {
     grid-column: span 6;
@@ -610,9 +611,7 @@ main .section.highlight {
 }
 
 .grid-item--33 {
-  @media (min-width: 22.5rem) {
-    grid-column: span 4;
-  }
+  grid-column: span 8;
 
   @media (min-width: 48rem) {
     grid-column: span 4;
@@ -620,9 +619,7 @@ main .section.highlight {
 }
 
 .grid-item--30 {
-  @media (min-width: 22.5rem) {
-    grid-column: span 2;
-  }
+  grid-column: span 4;
 
   @media (min-width: 48rem) {
     grid-column: span 3;
@@ -630,9 +627,7 @@ main .section.highlight {
 }
 
 .grid-item--25 {
-  @media (min-width: 22.5rem) {
-    grid-column: span 2;
-  }
+  grid-column: span 4;
 
   @media (min-width: 48rem) {
     grid-column: span 3;


Reviews:
user: login: catheraaine
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-05-07T13:21:20Z
user: login: catheraaine
body: Let's also make sure the spacing matches designs. That's 4rem top/bottom on all screen sizes, then looks like maybe 3rem between? 
<img width="1363" alt="Screenshot 2025-05-07 at 9 22 46 AM" src="https://github.com/user-attachments/assets/98546582-8e20-444a-b06c-be7cff54f402" />
<img width="592" alt="Screenshot 2025-05-07 at 9 23 01 AM" src="https://github.com/user-attachments/assets/d4a1fff1-fe02-4f57-8544-69f94bff0e3c" />
<img width="794" alt="Screenshot 2025-05-07 at 9 24 23 AM" src="https://github.com/user-attachments/assets/7fc6bfe6-3c39-44f8-bdaf-c56bb51bb4a8" />
<img width="633" alt="Screenshot 2025-05-07 at 9 25 04 AM" src="https://github.com/user-attachments/assets/a4bfbaf7-770b-4edd-ac24-57f41472c48f" />
<img width="367" alt="Screenshot 2025-05-07 at 9 24 48 AM" src="https://github.com/user-attachments/assets/eb5e549d-427f-4662-9500-c0dad2a3c862" />


state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-05-07T13:26:29Z
user: login: arnest00
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-08T19:53:33Z
user: login: catheraaine
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-08T20:17:48Z

Review comments:
diff_hunk: @@ -1,14 +1,15 @@
-const buildLinkListAnchor = ({ textContent, url }) => {
-  const linkListItem = document.createElement('a');
-  linkListItem.className = 'link-list-item';
-  linkListItem.href = url;
+const buildLinkListItem = ({ textContent, url }) => {
+  const linkListItem = document.createElement('li');
+  const linkListItemAnchor = document.createElement('a');
path: blocks/link-list/link-list.js
user: login: catheraaine
body: nice, clear update.
created_at: 2025-05-07T13:01:13Z
author_association: COLLABORATOR
diff_hunk: @@ -373,6 +373,53 @@ function decorateHorizontalRules(element) {
   });
 }
 
+/**
+ * Converts '<layout>' text nodes into containers to allow for nesting of blocks within layouts
+ * @param {Element} element container element
+ */
+function decorateLayouts(element) {
path: scripts/aem.js
user: login: catheraaine
body: You know we could make this file more readable by breaking all of these functions into partials and including them at the top of this file, then just running each function from this file.

Maybe we add a post-MVP ticket for AEM.js refactor, I'd love to do that clean up real quick!
created_at: 2025-05-07T13:03:19Z
author_association: COLLABORATOR
diff_hunk: @@ -373,6 +373,53 @@ function decorateHorizontalRules(element) {
   });
 }
 
+/**
+ * Converts '<layout>' text nodes into containers to allow for nesting of blocks within layouts
+ * @param {Element} element container element
+ */
+function decorateLayouts(element) {
+  // find all '<layout>' text nodes regardless of layout type
path: scripts/aem.js
user: login: catheraaine
body: I am going to send you a Slack message about the use of Angle brackets, let's talk through it.
created_at: 2025-05-07T13:12:38Z
author_association: COLLABORATOR
diff_hunk: @@ -373,6 +373,53 @@ function decorateHorizontalRules(element) {
   });
 }
 
+/**
+ * Converts '<layout>' text nodes into containers to allow for nesting of blocks within layouts
+ * @param {Element} element container element
+ */
+function decorateLayouts(element) {
+  // find all '<layout>' text nodes regardless of layout type
+  const layoutStarters = Array.from(element.querySelectorAll('p'))
+    .filter((p) => p.textContent.trim().startsWith('<layout'));
+
+  for (const layoutStarter of layoutStarters) {
+    const layoutWrapper = layoutStarter.parentNode;
+    let currentElement = layoutWrapper.nextElementSibling;
+    let layoutEnder = null;
+    const elementsToContain = [];
+
+    while (currentElement) {
+      if (currentElement.textContent.includes('</layout>')) {
+        // ensure only the <p> containing the layout ender is removed at the end of this iteration
+        // there may be a layout starter in the same <div>
+        layoutEnder = Array.from(currentElement.querySelectorAll('p'))
+          .find((p) => p.textContent.trim() === '</layout>');
+        break;
+      };
+
+      // TODO: support other layout types
path: scripts/aem.js
user: login: catheraaine
body: excellent
created_at: 2025-05-07T13:14:26Z
author_association: COLLABORATOR
diff_hunk: @@ -600,9 +600,7 @@ main .section.highlight {
 }
 
 .grid-item--50 {
-  @media (min-width: 22.5rem) {
-    grid-column: span 4;
-  }
+  grid-column: span 8;
path: styles/styles.css
user: login: catheraaine
body: I don't think my brain realized we are doing an 8-column grid instead of a 4-column grid when I wrote these up, or that changed since. **Can you either change the base grid on line 574 to be:** 

```css
grid-template-columns: repeat(4, [col] 1fr);
```

**or change the other classes to account for 8 columns**
`.grid-item--66`, `grid-item--33` small to be span 8

then this one needs to be at 50% so: 

```css
.grid-item--30 {
  @media (min-width: 22.5rem) {
    grid-column: span 4;
  }
```

created_at: 2025-05-07T13:21:16Z
author_association: COLLABORATOR
diff_hunk: @@ -373,6 +373,53 @@ function decorateHorizontalRules(element) {
   });
 }
 
+/**
+ * Converts '<layout>' text nodes into containers to allow for nesting of blocks within layouts
+ * @param {Element} element container element
+ */
+function decorateLayouts(element) {
path: scripts/aem.js
user: login: arnest00
body: Let's add a post-MVP ticket for that!
created_at: 2025-05-08T19:53:32Z
author_association: COLLABORATOR
diff_hunk: @@ -362,11 +362,11 @@ function wrapTextNodes(block) {
 function decorateHorizontalRules(element) {
   element.querySelectorAll('p').forEach((p) => {
     const content = p.textContent.trim();
-    if (content === '<hr>' || content === '<hr fw>') {
+    if (content === '-hr-' || content === '-hr fw-') {
       const wrapper = document.createElement('div');
       wrapper.className = 'horizontal-rule-wrapper';
       const hr = document.createElement('hr');
-      hr.className = content === '<hr fw>' ? 'horizontal-rule horizontal-rule--full' : 'horizontal-rule';
+      hr.className = content === '-hr fw-' ? 'horizontal-rule horizontal-rule--full' : 'horizontal-rule';
path: scripts/aem.js
user: login: catheraaine
body: great work
created_at: 2025-05-08T20:14:32Z
author_association: COLLABORATOR
diff_hunk: @@ -559,6 +559,10 @@ main .section.highlight {
 }
 
 /* ** Grid Layout Styles **  */
+main > .section > .grid-container {
+  margin-block: 4rem;
path: styles/styles.css
user: login: catheraaine
body: ohhhh excellent seriously so smart
created_at: 2025-05-08T20:16:02Z
author_association: COLLABORATOR

PR comments:
user: login: aem-code-sync[bot]
created_at: 2025-05-06T06:04:04Z
body: <!-- aem-bot-dash -->
Hello, I'm the AEM Code Sync Bot and I will run some actions to deploy your branch and validate page speed.
In case there are problems, just click a checkbox below to rerun the respective action.

- [ ] <!-- action-psi --> Re-run PSI checks
- [ ] <!-- action-sync --> Re-sync branch

<details>
<summary>Commits</summary>
<!-- commit-history -->

* [903c20a](https://github.com/adobe/adobe-design-website/commit/903c20a403e30ca3efe42530fc06010bcb74e544) :white_check_mark: (latest)
* [7d144c8](https://github.com/adobe/adobe-design-website/commit/7d144c87bec00370b2efb1175ca37801b00a1b3a) :white_check_mark:
* [8d99a17](https://github.com/adobe/adobe-design-website/commit/8d99a170181a6b6130106facd58192b3a4789451) :white_check_mark:
* [a99135a](https://github.com/adobe/adobe-design-website/commit/a99135a62fbbf78ae7e62d552ffd0acfd1bb0249) :white_check_mark:
* [876b403](https://github.com/adobe/adobe-design-website/commit/876b4036b005657134d2334cb417239fe4b6e3b7) :white_check_mark:
* [a28d9ca](https://github.com/adobe/adobe-design-website/commit/a28d9ca77c202bf56333f24880a4aaa763f68239) :white_check_mark:

<!-- commit-history-end -->
</details>
author_association: NONE
user: login: aem-code-sync[bot]
created_at: 2025-05-06T06:04:21Z
body: <!-- aem-bot-psi -->
| | Page | Scores | Audits | Google |
|-|------|--------|--------|--------|
| :iphone: | [/pattern-library/](https://feat-two-up-layout--adobe-design-website--adobe.aem.page/pattern-library/) | [![PERFORMANCE](https://img.shields.io/badge/PERFORMANCE-95-green?style=for-the-badge "Performance")](https://googlechrome.github.io/lighthouse/scorecalc/#device=mobile&version=12.6.0&SI=2420.2854&FCP=2420.2854&LCP=2420.2854&TBT=0&CLS=0 "See Calculator") ![A11Y](https://img.shields.io/badge/A11Y-91-green?style=for-the-badge "Accessibility") ![SEO](https://img.shields.io/badge/SEO-61-orange?style=for-the-badge "SEO") ![BEST PRACTICES](https://img.shields.io/badge/BEST%20PRACTICES-96-green?style=for-the-badge "Best Practices") | ![SI](https://img.shields.io/badge/SI-2.4s-green?style=for-the-badge "Speed Index") ![FCP](https://img.shields.io/badge/FCP-2.4s-orange?style=for-the-badge "First Contentful Paint") ![LCP](https://img.shields.io/badge/LCP-2.4s-green?style=for-the-badge "Largest Contentful Paint") ![TBT](https://img.shields.io/badge/TBT-0ms-green?style=for-the-badge "Total Blocking Time") ![CLS](https://img.shields.io/badge/CLS-0-green?style=for-the-badge "Cumulative Layout Shift") | [![PSI](https://www.gstatic.com/images/icons/material/product/2x/pagespeed_16dp.png)](https://developers.google.com/speed/pagespeed/insights/?url=https%3A%2F%2Ffeat-two-up-layout--adobe-design-website--adobe.aem.page%2Fpattern-library%2F "Run again in Google PSI") |
| :desktop_computer: | [/pattern-library/](https://feat-two-up-layout--adobe-design-website--adobe.aem.page/pattern-library/) | [![PERFORMANCE](https://img.shields.io/badge/PERFORMANCE-100-green?style=for-the-badge "Performance")](https://googlechrome.github.io/lighthouse/scorecalc/#device=mobile&version=12.6.0&SI=616.1608&FCP=616.1608&LCP=636.92085&TBT=0&CLS=0 "See Calculator") ![A11Y](https://img.shields.io/badge/A11Y-91-green?style=for-the-badge "Accessibility") ![SEO](https://img.shields.io/badge/SEO-61-orange?style=for-the-badge "SEO") ![BEST PRACTICES](https://img.shields.io/badge/BEST%20PRACTICES-96-green?style=for-the-badge "Best Practices") | ![SI](https://img.shields.io/badge/SI-0.6s-green?style=for-the-badge "Speed Index") ![FCP](https://img.shields.io/badge/FCP-0.6s-green?style=for-the-badge "First Contentful Paint") ![LCP](https://img.shields.io/badge/LCP-0.6s-green?style=for-the-badge "Largest Contentful Paint") ![TBT](https://img.shields.io/badge/TBT-0ms-green?style=for-the-badge "Total Blocking Time") ![CLS](https://img.shields.io/badge/CLS-0-green?style=for-the-badge "Cumulative Layout Shift") | [![PSI](https://www.gstatic.com/images/icons/material/product/2x/pagespeed_16dp.png)](https://developers.google.com/speed/pagespeed/insights/?url=https%3A%2F%2Ffeat-two-up-layout--adobe-design-website--adobe.aem.page%2Fpattern-library%2F "Run again in Google PSI") |
| :iphone: | [/pattern-library/layouts](https://feat-two-up-layout--adobe-design-website--adobe.aem.page/pattern-library/layouts) | [![PERFORMANCE](https://img.shields.io/badge/PERFORMANCE-92-green?style=for-the-badge "Performance")](https://googlechrome.github.io/lighthouse/scorecalc/#device=mobile&version=12.6.0&SI=2702.5&FCP=2702.5&LCP=2702.5&TBT=0&CLS=0.0005282743452304308 "See Calculator") ![A11Y](https://img.shields.io/badge/A11Y-95-green?style=for-the-badge "Accessibility") ![SEO](https://img.shields.io/badge/SEO-69-orange?style=for-the-badge "SEO") ![BEST PRACTICES](https://img.shields.io/badge/BEST%20PRACTICES-100-green?style=for-the-badge "Best Practices") | ![SI](https://img.shields.io/badge/SI-2.7s-green?style=for-the-badge "Speed Index") ![FCP](https://img.shields.io/badge/FCP-2.7s-orange?style=for-the-badge "First Contentful Paint") ![LCP](https://img.shields.io/badge/LCP-2.7s-orange?style=for-the-badge "Largest Contentful Paint") ![TBT](https://img.shields.io/badge/TBT-0ms-green?style=for-the-badge "Total Blocking Time") ![CLS](https://img.shields.io/badge/CLS-0.001-green?style=for-the-badge "Cumulative Layout Shift") | [![PSI](https://www.gstatic.com/images/icons/material/product/2x/pagespeed_16dp.png)](https://developers.google.com/speed/pagespeed/insights/?url=https%3A%2F%2Ffeat-two-up-layout--adobe-design-website--adobe.aem.page%2Fpattern-library%2Flayouts "Run again in Google PSI") |
| :desktop_computer: | [/pattern-library/layouts](https://feat-two-up-layout--adobe-design-website--adobe.aem.page/pattern-library/layouts) | [![PERFORMANCE](https://img.shields.io/badge/PERFORMANCE-100-green?style=for-the-badge "Performance")](https://googlechrome.github.io/lighthouse/scorecalc/#device=mobile&version=12.6.0&SI=636.29018&FCP=636.29018&LCP=636.29018&TBT=0&CLS=0.0047391108284986026 "See Calculator") ![A11Y](https://img.shields.io/badge/A11Y-95-green?style=for-the-badge "Accessibility") ![SEO](https://img.shields.io/badge/SEO-69-orange?style=for-the-badge "SEO") ![BEST PRACTICES](https://img.shields.io/badge/BEST%20PRACTICES-100-green?style=for-the-badge "Best Practices") | ![SI](https://img.shields.io/badge/SI-0.6s-green?style=for-the-badge "Speed Index") ![FCP](https://img.shields.io/badge/FCP-0.6s-green?style=for-the-badge "First Contentful Paint") ![LCP](https://img.shields.io/badge/LCP-0.6s-green?style=for-the-badge "Largest Contentful Paint") ![TBT](https://img.shields.io/badge/TBT-0ms-green?style=for-the-badge "Total Blocking Time") ![CLS](https://img.shields.io/badge/CLS-0.005-green?style=for-the-badge "Cumulative Layout Shift") | [![PSI](https://www.gstatic.com/images/icons/material/product/2x/pagespeed_16dp.png)](https://developers.google.com/speed/pagespeed/insights/?url=https%3A%2F%2Ffeat-two-up-layout--adobe-design-website--adobe.aem.page%2Fpattern-library%2Flayouts "Run again in Google PSI") |

author_association: NONE
user: login: catheraaine
created_at: 2025-05-08T02:26:14Z
body: you are probably going to have to turn off the cards tests until that work gets merged
author_association: COLLABORATOR