From ac6939cbcb66b0fc90476dd9a8b0eb2f59fd5c70 Mon Sep 17 00:00:00 2001 From: James Rosewell Date: Wed, 2 Sep 2026 14:15:39 +0100 Subject: [PATCH 1/8] Add an editor component to the examples design system An example that lets a reader type something structured and see what it does needs a multi line box, and there was no component for one, so the derived property script tester had been styling its own. c-eg-editor reuses the design system input chrome and the documentation code face rather than restating either, so an editor sits beside a b-input in the same form without looking like a different control. What it adds is only what a single line input has no reason to carry. A minimum height, because an editor opening one line tall is unusable and every example using one wants the same starting size. A vertical only resize grip. Widening one editor in a two column layout would push the other out of the grid. A two space tab stop, because the formats these examples show are indented two spaces and a tab jumping eight columns makes pasted content look wrong. Lines that scroll sideways rather than wrap. Wrapping a structured format hides the indentation that carries its meaning. Written generally rather than for the one page that needs it today, in the way the status component was. --- .../source/sass/06-examples/08-editor.scss | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 pattern-library/source/sass/06-examples/08-editor.scss diff --git a/pattern-library/source/sass/06-examples/08-editor.scss b/pattern-library/source/sass/06-examples/08-editor.scss new file mode 100644 index 000000000..abba59e8b --- /dev/null +++ b/pattern-library/source/sass/06-examples/08-editor.scss @@ -0,0 +1,37 @@ +/** + * A multi line editor for an example that lets a reader type something + * structured and see what it does, such as a script, a configuration + * block or a request body. + * + * It reuses the design system input chrome and the documentation code + * face rather than inventing either, so an editor sits beside a .b-input + * in the same form without looking like a different control. What it adds + * is only what a single line input has no reason to carry: room to work + * in, a scrollbar rather than wrapped lines, a tab that lines up with the + * two space indent our formats use, and a grip to make the box taller. + * + * Lines are not wrapped. Wrapping a structured format hides the + * indentation that carries its meaning, so a long line scrolls sideways + * and stays readable as one line. + */ + +.c-eg-editor { + @include make-input(); + @include make-text-code(); + + width: 100%; + min-height: 340px; + background-color: $colour-grey-lighter; + + /* Only the height can be dragged. A reader widening one editor in a + two column layout would push the other out of the grid. */ + resize: vertical; + + /* The formats these examples show are indented two spaces, so a tab + that jumps eight columns makes pasted content look wrong. */ + tab-size: 2; + + /* Long lines scroll rather than wrap, as above. */ + white-space: pre; + overflow: auto; +} From e8483c1a3e0c3c00f2aefdf844cde52c2e2d58a8 Mon Sep 17 00:00:00 2001 From: James Rosewell Date: Wed, 2 Sep 2026 14:58:34 +0100 Subject: [PATCH 2/8] Give the editor rounded corners, a code line height and a focus ring Three things a multi line editor needs that the first version left out. Rounded corners using $rounded, matching every other rounded surface. A line height of 1.5. make-input sets 1.2, which is right for one line of prose and too tight for many lines of indented code. A focus ring on focus-visible, because an editor is where a reader is typing and which box holds the caret has to be obvious. A keyboard user gets the ring and a pointer user clicking into the box does not. The border softens to $colour-grey-light so the box reads as an area to work in rather than as a bordered rectangle. --- .../source/sass/06-examples/08-editor.scss | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/pattern-library/source/sass/06-examples/08-editor.scss b/pattern-library/source/sass/06-examples/08-editor.scss index abba59e8b..24a0371e9 100644 --- a/pattern-library/source/sass/06-examples/08-editor.scss +++ b/pattern-library/source/sass/06-examples/08-editor.scss @@ -21,7 +21,17 @@ width: 100%; min-height: 340px; + + /* Set back from the page rather than sitting on it, so a reader sees + an area to work in rather than a bordered rectangle. The corners + match every other rounded surface in the system. */ background-color: $colour-grey-lighter; + border-color: $colour-grey-light; + border-radius: $rounded; + + /* make-input sets 1.2, which is right for one line of prose and too + tight for many lines of indented code. */ + line-height: 1.5; /* Only the height can be dragged. A reader widening one editor in a two column layout would push the other out of the grid. */ @@ -34,4 +44,14 @@ /* Long lines scroll rather than wrap, as above. */ white-space: pre; overflow: auto; + + /* An editor is where a reader is typing, so which box has the caret + has to be obvious. Keyboard users get the ring, and a pointer user + clicking straight into the box does not, which is what + :focus-visible is for. */ + &:focus-visible { + outline: 2px solid $colour-blue; + outline-offset: 1px; + border-color: $colour-blue; + } } From 8e1af34377be5354f4d1857149c6ca50cbc9006b Mon Sep 17 00:00:00 2001 From: James Rosewell Date: Wed, 2 Sep 2026 15:56:47 +0100 Subject: [PATCH 3/8] Give examples a wider shell and controls a reader can use Three additions, from building an example that takes input. c-eg-page--wide widens the shell to 1400px. 800px is a comfortable measure for reading a column of prose, which is what nearly every example is, and it is far too narrow once a page puts two columns or a table of controls inside it, because the columns halve it and every control in them collapses and truncates. Prose keeps the 800px measure rather than stretching with it. c-eg-control is a single line box or list. It asks for a minimum width, which is the part that was missing, because a control in a narrow table column otherwise collapses to the column and truncates what it holds. c-eg-controls lays out a row of things that belong together, such as a label, a list and the button that acts on it. c-eg-button-row sets margins and nothing else, which is all a row of buttons needs, and it leaves a control filling the line with the button underneath. 08-editor.scss becomes 08-fields.scss and holds all three, since the editor and the control share one treatment, now extracted as make-example-field. The gap is general rather than specific to one page. Almost every example we ship reports data and asks for nothing, so the system grew a rich set of ways to display a result and nothing for a reader to type into. --- .../source/sass/06-examples/00-page.scss | 20 ++++ .../source/sass/06-examples/08-editor.scss | 57 ----------- .../source/sass/06-examples/08-fields.scss | 99 +++++++++++++++++++ 3 files changed, 119 insertions(+), 57 deletions(-) delete mode 100644 pattern-library/source/sass/06-examples/08-editor.scss create mode 100644 pattern-library/source/sass/06-examples/08-fields.scss diff --git a/pattern-library/source/sass/06-examples/00-page.scss b/pattern-library/source/sass/06-examples/00-page.scss index dcd7046b8..4e267e5b9 100644 --- a/pattern-library/source/sass/06-examples/00-page.scss +++ b/pattern-library/source/sass/06-examples/00-page.scss @@ -9,6 +9,26 @@ padding: size(2) size(1); } +/** + * A wider shell for an example that has to show two things beside each + * other, such as what a reader typed and what came back. + * + * 800px is a comfortable measure for reading a column of prose, which is + * what most examples are. It is far too narrow once a page puts two + * columns or a table of controls inside it, because the columns then + * halve it and every control in them collapses and truncates. + * + * The prose keeps the reading measure rather than stretching to the new + * width, so only the parts that need the room take it. + */ +.c-eg-page--wide { + max-width: 1400px; + + > p { + max-width: 800px; + } +} + .c-eg-page__title { @include make-text-heading-2(); } diff --git a/pattern-library/source/sass/06-examples/08-editor.scss b/pattern-library/source/sass/06-examples/08-editor.scss deleted file mode 100644 index 24a0371e9..000000000 --- a/pattern-library/source/sass/06-examples/08-editor.scss +++ /dev/null @@ -1,57 +0,0 @@ -/** - * A multi line editor for an example that lets a reader type something - * structured and see what it does, such as a script, a configuration - * block or a request body. - * - * It reuses the design system input chrome and the documentation code - * face rather than inventing either, so an editor sits beside a .b-input - * in the same form without looking like a different control. What it adds - * is only what a single line input has no reason to carry: room to work - * in, a scrollbar rather than wrapped lines, a tab that lines up with the - * two space indent our formats use, and a grip to make the box taller. - * - * Lines are not wrapped. Wrapping a structured format hides the - * indentation that carries its meaning, so a long line scrolls sideways - * and stays readable as one line. - */ - -.c-eg-editor { - @include make-input(); - @include make-text-code(); - - width: 100%; - min-height: 340px; - - /* Set back from the page rather than sitting on it, so a reader sees - an area to work in rather than a bordered rectangle. The corners - match every other rounded surface in the system. */ - background-color: $colour-grey-lighter; - border-color: $colour-grey-light; - border-radius: $rounded; - - /* make-input sets 1.2, which is right for one line of prose and too - tight for many lines of indented code. */ - line-height: 1.5; - - /* Only the height can be dragged. A reader widening one editor in a - two column layout would push the other out of the grid. */ - resize: vertical; - - /* The formats these examples show are indented two spaces, so a tab - that jumps eight columns makes pasted content look wrong. */ - tab-size: 2; - - /* Long lines scroll rather than wrap, as above. */ - white-space: pre; - overflow: auto; - - /* An editor is where a reader is typing, so which box has the caret - has to be obvious. Keyboard users get the ring, and a pointer user - clicking straight into the box does not, which is what - :focus-visible is for. */ - &:focus-visible { - outline: 2px solid $colour-blue; - outline-offset: 1px; - border-color: $colour-blue; - } -} diff --git a/pattern-library/source/sass/06-examples/08-fields.scss b/pattern-library/source/sass/06-examples/08-fields.scss new file mode 100644 index 000000000..254f78ac1 --- /dev/null +++ b/pattern-library/source/sass/06-examples/08-fields.scss @@ -0,0 +1,99 @@ +/** + * Fields for an example that takes input, rather than one that only + * shows what an engine returned. + * + * Almost every example we ship reports data and asks for nothing, so the + * system grew a rich set of ways to display a result and nothing for a + * reader to type into. An example that does take input was left composing + * .b-input with whatever it could find, which reads as a different + * control on the same page. These are the two fields such an example + * needs, being a single line control and a multi line editor, sharing one + * treatment so they look like a set. + */ + +/** + * What both fields share on top of the design system input. The border + * softens and the corners round, so a field reads as an area to work in + * rather than as a bordered rectangle, and a focus ring says which field + * holds the caret. + * + * The ring is on :focus-visible, so a keyboard user gets it and a + * pointer user clicking straight into the field does not. + */ +@mixin make-example-field { + @include make-input(); + border-color: $colour-grey-light; + border-radius: $rounded; + background-color: $colour-grey-lighter; + + &:focus-visible { + outline: 2px solid $colour-blue; + outline-offset: 1px; + border-color: $colour-blue; + } +} + +/** + * A single line control, being a text box or a list. Filling its + * container is not enough on its own, because a control in a narrow + * table column would collapse to the column and truncate what it holds, + * so it asks for a width the column has to give it. + */ +.c-eg-control { + @include make-example-field(); + width: 100%; + min-width: size(11); +} + +/** + * A row of controls that belong together, such as a label, a list to + * choose from and the button that acts on it. + * + * c-eg-button-row sets margins and nothing else, which is all a row of + * buttons needs, and it leaves a control filling the line on its own so + * the button beside it falls underneath. This lays the row out. + */ +.c-eg-controls { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: size(0); + margin: size(1) 0 size(2); + + /* A control in a row sizes to what it holds rather than filling the + line, which is the opposite of what it does in a table cell or a + column, so the row stays a row. */ + .c-eg-control { + width: auto; + } +} + +/** + * A multi line editor for something structured, such as a script, a + * configuration block or a request body. It adds to the single line + * control only what many lines need. + */ +.c-eg-editor { + @include make-example-field(); + @include make-text-code(); + + width: 100%; + min-height: 340px; + + /* make-input sets 1.2, which is right for one line of prose and too + tight for many lines of indented code. */ + line-height: 1.5; + + /* Only the height can be dragged. A reader widening one editor in a + two column layout would push the other out of the grid. */ + resize: vertical; + + /* The formats these examples show are indented two spaces, so a tab + that jumps eight columns makes pasted content look wrong. */ + tab-size: 2; + + /* Lines scroll sideways rather than wrap, because wrapping a + structured format hides the indentation that carries its meaning. */ + white-space: pre; + overflow: auto; +} From e070d89014ed138e69f6ed682cb06a407e3ea6ef Mon Sep 17 00:00:00 2001 From: James Rosewell Date: Wed, 2 Sep 2026 16:39:12 +0100 Subject: [PATCH 4/8] Make a table of example fields read as one size A row of fields held three sizes, because the system sizes each piece for where it usually appears. A control carries the body size it needs standing alone in a form, the code face is a step smaller because it usually sits inside running prose, and cell text is neither. Put all three in one row and a reader sees three different things. c-eg-fields makes everything inside it take the size of the table, and drops the control back to the cell padding, because a row of controls is dense by nature and padding sized for a lone box makes the table tall for no reason. --- .../source/sass/06-examples/08-fields.scss | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/pattern-library/source/sass/06-examples/08-fields.scss b/pattern-library/source/sass/06-examples/08-fields.scss index 254f78ac1..6276d879b 100644 --- a/pattern-library/source/sass/06-examples/08-fields.scss +++ b/pattern-library/source/sass/06-examples/08-fields.scss @@ -45,6 +45,31 @@ min-width: size(11); } +/** + * A table of fields, being one row for each thing a reader sets. + * + * Everything inside it is one size. The system sizes each piece for the + * place it usually appears, so a control carries the body size it needs + * standing alone in a form, and the code face is a step smaller because + * it usually sits inside running prose. Put the two in one row with + * ordinary cell text and a reader sees three sizes and reads three + * different things rather than one row. + * + * The control also loses its form padding, because a row of them is + * dense by nature and padding sized for a lone box makes the table tall + * enough to scroll for no reason. + */ +.c-eg-fields { + .c-eg-control, + .b-text--code { + font-size: inherit; + } + + .c-eg-control { + padding: size(-3) size(-2); + } +} + /** * A row of controls that belong together, such as a label, a list to * choose from and the button that acts on it. From 01fe7194ec7df930d9013d89f1102420f1eee73d Mon Sep 17 00:00:00 2001 From: James Rosewell Date: Wed, 2 Sep 2026 16:49:23 +0100 Subject: [PATCH 5/8] Align a field row on its middle rather than its top A table aligns to the top, which is right where a cell holds a paragraph and the row is as tall as the longest one. In a field table the tall cells are controls, which centre what they hold, so a name aligned to the top sits above the control beside it and nothing in the row lines up. --- pattern-library/source/sass/06-examples/08-fields.scss | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pattern-library/source/sass/06-examples/08-fields.scss b/pattern-library/source/sass/06-examples/08-fields.scss index 6276d879b..f2d6990ce 100644 --- a/pattern-library/source/sass/06-examples/08-fields.scss +++ b/pattern-library/source/sass/06-examples/08-fields.scss @@ -68,6 +68,15 @@ .c-eg-control { padding: size(-3) size(-2); } + + /* A table aligns to the top, which is right where a cell holds a + paragraph and the row is as tall as the longest one. Here the tall + cells are controls, which centre what they hold, so a name aligned + to the top sits above the control beside it and nothing in the row + lines up. */ + .c-eg-table__cell { + vertical-align: middle; + } } /** From 737c6a8f01d384159d330e5c8238296a0b591c7b Mon Sep 17 00:00:00 2001 From: James Rosewell Date: Wed, 2 Sep 2026 16:53:04 +0100 Subject: [PATCH 6/8] Add a block for output printed exactly as something produced it An example that prints a message or a canonical form was using a bare pre, and a long line inside one widens its container. On a page laid out in columns that pushes everything else out of shape, which is worse than a scrollbar. c-eg-pre scrolls sideways instead and carries the code face and the same soft background as the fields, so printed output reads as part of the same set. c-eg-pre--wrap is for output that is prose rather than structure, such as a message. Prose is easier to read wrapped and something with meaningful indentation is not, which is why it is a choice rather than the default. --- .../source/sass/06-examples/08-fields.scss | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/pattern-library/source/sass/06-examples/08-fields.scss b/pattern-library/source/sass/06-examples/08-fields.scss index f2d6990ce..e180cbd8f 100644 --- a/pattern-library/source/sass/06-examples/08-fields.scss +++ b/pattern-library/source/sass/06-examples/08-fields.scss @@ -79,6 +79,36 @@ } } +/** + * Output printed exactly as something produced it, such as a message a + * caller would meet or a canonical form. + * + * It scrolls sideways rather than stretching what is around it. A long + * line inside a bare pre widens its container, and in a page laid out in + * columns that pushes everything else out of shape, which is worse than + * a scrollbar. + */ +.c-eg-pre { + @include make-text-code(); + background-color: $colour-grey-lighter; + border-radius: $rounded; + padding: size(0); + margin: 0 0 size(2); + max-width: 100%; + overflow-x: auto; +} + +/** + * The same block for output that is prose rather than structure, such as + * a message. Prose is easier to read wrapped, whilst something with + * meaningful indentation is not, which is why this is a choice rather + * than the default. + */ +.c-eg-pre--wrap { + white-space: pre-wrap; + word-break: break-word; +} + /** * A row of controls that belong together, such as a label, a list to * choose from and the button that acts on it. From 0dd4ae4ef9488758025b76e74f022505030eb519 Mon Sep 17 00:00:00 2001 From: James Rosewell Date: Wed, 2 Sep 2026 17:18:17 +0100 Subject: [PATCH 7/8] Add a result panel for the answer an example gives An example computes one thing and everything else on its page explains that one thing, so the answer is what a reader looks for first and comes back to after every change. Reported at the size of ordinary body text it reads as one more paragraph among the paragraphs explaining it. c-eg-result is a panel for that answer, with a quiet label, the value in the code face at a size that makes it the first thing seen, and a quiet note underneath for how to read it. The modifier carries the case where there is no answer, because there is no value to colour and a reader scanning for one needs to see at a glance that there is not one. Put it above whatever a reader changes rather than below, so that changing something and seeing what it did takes no scrolling. --- .../source/sass/06-examples/09-result.scss | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 pattern-library/source/sass/06-examples/09-result.scss diff --git a/pattern-library/source/sass/06-examples/09-result.scss b/pattern-library/source/sass/06-examples/09-result.scss new file mode 100644 index 000000000..3475355b5 --- /dev/null +++ b/pattern-library/source/sass/06-examples/09-result.scss @@ -0,0 +1,65 @@ +/** + * The answer an example exists to give, at the weight that deserves. + * + * An example computes one thing, and everything else on its page is + * there to explain that one thing. So the answer is what a reader looks + * for first and what they come back to after every change they make. + * Reported at the size of ordinary body text it reads as one more + * paragraph among the paragraphs explaining it, and a reader has to hunt + * for the part they came for. + * + * Put this above whatever a reader changes rather than below it, so that + * changing something and seeing what it did takes no scrolling. + */ + +.c-eg-result { + background-color: $colour-grey-lighter; + border-radius: $rounded; + padding: size(2) size(3); + margin: size(1) 0 size(3); +} + +/* What the answer is the answer to, said quietly above it, because the + reader already knows and only needs reminding. */ +.c-eg-result__label { + @include make-text-heading-caps(); + display: block; + margin: 0 0 size(-2); +} + +/* The answer itself. It carries the code face because what an example + returns is a value rather than prose, and a size that makes it the + first thing on the page a reader sees. */ +.c-eg-result__value { + @include make-text-code(); + font-size: size(7); + line-height: 1.1; + color: $colour-black; + margin: 0; + + /* A value can be a long string, and stretching the panel to fit one + would push the page out of shape. */ + word-break: break-word; +} + +/* How the answer should be read, said quietly below it. */ +.c-eg-result__note { + margin: size(0) 0 0; +} + +/** + * Where the example has no answer to give. The surface carries it rather + * than the value, because there is no value to colour, and a reader + * scanning for the answer needs to see at a glance that there is not + * one. + */ +.c-eg-result--none { + background-color: $colour-pink-light; + border: 1px solid $colour-red; + + .c-eg-result__value { + @include make-text-default(); + font-weight: $font-weight-bold; + color: $colour-red; + } +} From 8c312070a0902b9f7216343e040c5eed62c1f3f8 Mon Sep 17 00:00:00 2001 From: James Rosewell Date: Wed, 2 Sep 2026 18:26:48 +0100 Subject: [PATCH 8/8] Let an example use the width it asked for A wide example page kept every paragraph at the 800px reading measure, so a page that asked for 1400px because it has two columns and a table of controls in it still ran its prose down the left hand half and left the right hand half empty. The measure is worth keeping, but keeping it by narrowing one paragraph at a time wastes the room the page asked for. The measure now comes from dividing the width rather than from leaving it unused. c-eg-intro lays the notes an example opens with across two columns from md and three from xl, so what a page has to say before it starts takes one band and the example itself is on screen without scrolling, and each column is still a readable width. c-eg-masthead puts the logo beside the title and the lead rather than above them, which gives back the height of the logo. Nothing inside c-eg-page--wide is narrowed any more, so an example that puts something wide in the page gets the width without having to override a rule from here. --- .../source/sass/06-examples/00-page.scss | 65 ++++++++++++++++++- 1 file changed, 62 insertions(+), 3 deletions(-) diff --git a/pattern-library/source/sass/06-examples/00-page.scss b/pattern-library/source/sass/06-examples/00-page.scss index 4e267e5b9..148d2b755 100644 --- a/pattern-library/source/sass/06-examples/00-page.scss +++ b/pattern-library/source/sass/06-examples/00-page.scss @@ -18,14 +18,73 @@ * columns or a table of controls inside it, because the columns then * halve it and every control in them collapses and truncates. * - * The prose keeps the reading measure rather than stretching to the new - * width, so only the parts that need the room take it. + * Everything inside takes the full width. Prose that would otherwise run + * to an unreadable measure goes in c-eg-intro or c-eg-columns below, + * which divide the width into readable columns rather than leaving half + * the page empty. */ .c-eg-page--wide { max-width: 1400px; +} + +/** + * The notes an example opens with, laid across the width rather than + * stacked down the left of it. + * + * A wide page fits three columns of prose beside each other, so what an + * example has to say before it starts takes one band and the example + * itself is on screen without scrolling. Each column keeps a readable + * measure because the columns divide the width, rather than one + * paragraph stretching across all of it. + */ +.c-eg-intro { + display: grid; + grid-template-columns: 1fr; + gap: 0 size(2); + margin-bottom: size(2); + + @include respond-to(md) { + grid-template-columns: 1fr 1fr; + } + + @include respond-to(xl) { + grid-template-columns: repeat(3, 1fr); + } > p { - max-width: 800px; + margin-top: 0; + } +} + +/** + * The head of an example, being the logo beside the title and the line + * that says what the example is for. + * + * The logo sits beside the words rather than above them, because a logo + * on its own line costs every reader the height of it before anything + * they came for. + */ +.c-eg-masthead { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: size(0) size(3); + margin-bottom: size(2); + + > img { + flex: 0 0 auto; + } + + &__words { + flex: 1 1 20rem; + + > :first-child { + margin-top: 0; + } + + > :last-child { + margin-bottom: 0; + } } }