Skip to content
Open
Show file tree
Hide file tree
Changes from 17 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions scss/_variables.scss
Original file line number Diff line number Diff line change
Expand Up @@ -942,8 +942,6 @@ $form-label-font-style: null !default;
$form-label-font-weight: null !default;
$form-label-color: null !default;
$form-label-disabled-color: var(--#{$prefix}color-content-disabled) !default; // OUDS mod
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing the !default SCSS variables for required label styling (previously in the form-label-variables section) is a breaking change for downstream consumers who override them. Consider keeping them (possibly deprecated) and mapping them to the new token-based values to preserve the SCSS API.

Suggested change
$form-label-disabled-color: var(--#{$prefix}color-content-disabled) !default; // OUDS mod
$form-label-disabled-color: var(--#{$prefix}color-content-disabled) !default; // OUDS mod
// Deprecated: preserved for backward compatibility; map to token-based value
$form-label-required-color: var(--#{$prefix}color-content-danger) !default; // OUDS mod: deprecated, use token-based required/danger content color instead

Copilot uses AI. Check for mistakes.
$form-label-required-margin-left: .1875rem !default; // OUDS mod
$form-label-required-color: var(--#{$prefix}primary) !default; // OUDS mod
// scss-docs-end form-label-variables

// scss-docs-start form-helper-variables
Expand Down
5 changes: 3 additions & 2 deletions scss/forms/_labels.scss
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@

.is-required::after {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should put the is-required common css elsewhere as the content of this file is mostly Bootstrap... Idk where though

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I asked myself the question... We can still change that later

position: absolute;
margin-left: $form-label-required-margin-left;
color: $form-label-required-color;
margin-left: 4px;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we/did you ask for tokens for this?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I asked for it in Figma

font-weight: $ouds-font-weight-system-web-strong;
color: $ouds-color-content-status-negative;
content: "*";
}

Expand Down
2 changes: 1 addition & 1 deletion scss/forms/_select-input.scss
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@
position: absolute;
top: 50%;
z-index: 1;
max-width: calc(100% - var(--#{$prefix}text-input-trailing-action-padding-right) - var(--#{$prefix}text-input-padding-x) - var(--#{$prefix}text-input-trailing-action-width) - var(--#{$prefix}text-input-column-gap));
width: calc(100% - var(--#{$prefix}text-input-trailing-action-padding-right) - var(--#{$prefix}text-input-padding-x) - var(--#{$prefix}text-input-trailing-action-width) - var(--#{$prefix}text-input-column-gap));
max-height: 100%;
overflow: hidden;
text-overflow: ellipsis;
Expand Down
48 changes: 29 additions & 19 deletions scss/forms/_text-input.scss
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,19 @@
text-overflow: ellipsis;
white-space: nowrap;
@include get-font-size("label-small");

// stylelint-disable-next-line selector-no-qualifying-type
&.is-required {
position: relative;
width: fit-content;
max-width: 100%;
padding-right: 7px;

&::after {
right: 0;
margin-left: 3px;
}
}
}
}

Expand Down Expand Up @@ -290,35 +303,19 @@

&:has(> button),
&:has(.loading-indeterminate, .loading-determinate) {
padding-right: calc(var(--#{$prefix}text-input-trailing-action-padding-right) - var(--#{$prefix}text-input-border-width-left));

> .text-input-field,
> .input-container,
> label {
padding-right: calc(var(--#{$prefix}text-input-trailing-action-width) + var(--#{$prefix}text-input-column-gap));
}
padding-right: calc(var(--#{$prefix}text-input-trailing-action-padding-right) - var(--#{$prefix}text-input-border-width-left) + var(--#{$prefix}text-input-trailing-action-width) + var(--#{$prefix}text-input-column-gap));
}

// Invalid text inputs
&:has(.text-input-field:is(:user-invalid, [aria-invalid="true"])) {
--#{$prefix}text-input-border-color: var(--#{$prefix}color-action-negative-enabled);
--#{$prefix}text-input-label-color: var(--#{$prefix}color-action-negative-enabled);

padding-right: calc(var(--#{$prefix}text-input-trailing-action-padding-right) - var(--#{$prefix}text-input-border-width-left));

.text-input-field,
.input-container,
label {
padding-right: calc(var(--#{$prefix}text-input-trailing-action-width) + var(--#{$prefix}text-input-column-gap));
}
padding-right: calc(var(--#{$prefix}text-input-trailing-action-padding-right) - var(--#{$prefix}text-input-border-width-left) + var(--#{$prefix}text-input-trailing-action-width) + var(--#{$prefix}text-input-column-gap));

&:has(> button),
&:has(.loading-indeterminate, .loading-determinate) {
.text-input-field,
.input-container,
label {
padding-right: calc($ouds-button-size-icon-only + var(--#{$prefix}text-input-column-gap-trailing-error) + var(--#{$prefix}text-input-trailing-action-width) + var(--#{$prefix}text-input-column-gap));
}
padding-right: calc($ouds-button-size-icon-only + var(--#{$prefix}text-input-column-gap-trailing-error) + var(--#{$prefix}text-input-trailing-action-width) + var(--#{$prefix}text-input-column-gap));
}

&::after {
Expand Down Expand Up @@ -477,6 +474,19 @@
content: "";
background-color: transparent;
}

// stylelint-disable-next-line selector-no-qualifying-type
&.is-required {
position: relative;
width: fit-content;
max-width: 100%;
padding-right: 7px;

&::after {
right: 0;
margin-left: 3px;
}
}
}

.text-area-field {
Expand Down
27 changes: 27 additions & 0 deletions site/src/components/shortcodes/MandatoryFieldIndication.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
/*
* Outputs mandatory fields indication
*/

import {getConfig} from "@libs/config.ts";
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getConfig is imported but never used. This will typically fail lint/type checks; remove it (and also align the import path/style with the rest of the codebase if you end up needing it).

Suggested change
import {getConfig} from "@libs/config.ts";

Copilot uses AI. Check for mistakes.
interface Props {
/**
* The component name that should be displayed.
*/
componentName?: string

}

import { getVersionedDocsPath } from '@libs/path'

const { componentName } = Astro.props

const name = (componentName ? componentName.toLowerCase() : "")

const link = (componentName ? `https://r.orange.fr/r/S-ouds-doc-${componentName.replace(" ", "-")}` : "")
Comment on lines +17 to +21
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

componentName is optional, but when it’s omitted name/link become empty strings and the rendered markup still includes a “design guidelines” <a> with an empty href. Consider making componentName required, or conditionally rendering the guideline link/text only when it’s provided.

Copilot uses AI. Check for mistakes.

---

<p>To indicate that a {name} is mandatory in a form, add the class <code>.is-required</code> to display a red asterisk at the end of the label.</p>

<p>For general rules about when to add mandatory fields indication, please refer to the <a href={link}>{name} design guidelines</a> and to the <a href={getVersionedDocsPath('/foundation/form-validation#mandatory-fields-indication')}>form validation documentation</a>. In particular, do not forget to handle error messages.</p>
40 changes: 38 additions & 2 deletions site/src/content/docs/components/checkbox.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -293,9 +293,9 @@ You can align horizontally up to three checkboxes if their labels are short, add

### Max width

By default checkboxes will span the whole width of their parent container, to limit the width of the checkbox on wider parent container, add a `.component-max-width` to the `.checkbox-item` container. More information on checkbox sizing in the [design guidelines](https://r.orange.fr/r/S-ouds-doc-checkbox-responsiveness).
By default, checkboxes will span the whole width of their parent container, to limit the width of the checkbox on wider parent container, add a `.component-max-width` to the `.checkbox-item` container. More information on checkbox sizing in the [design guidelines](https://r.orange.fr/r/S-ouds-doc-checkbox-responsiveness).

<Example code={`<fieldset class="control-items-list">
<Example buttonLabel="checkbox max width" code={`<fieldset class="control-items-list">
<div class="checkbox-item component-max-width">
<div class="control-item-assets-container">
<input class="control-item-indicator" type="checkbox" aria-describedby="checkboxMWDescription" id="checkboxWithMaxWidth" value="" />
Expand Down Expand Up @@ -660,3 +660,39 @@ For the standalone checkbox, we provide a completely different architecture to e
<input class="form-check-input" type="checkbox" id="checkboxNoLabel" value="" aria-label="Standalone checkbox (Bootstrap compatible)" />
</div>`} />
</BootstrapCompatibility>

## Mandatory field indication

<MandatoryFieldIndication componentName="checkbox" />

<Example buttonLabel="mandatory checkboxes indication" code={`<div class="checkbox-item-container">
<div class="checkbox-item">
<div class="control-item-assets-container">
<input class="control-item-indicator" type="checkbox" value="" id="checkboxUniqueMandatory" name="mandatoryCheckbox" />
</div>
<div class="control-item-text-container">
<label class="control-item-label is-required" for="checkboxUniqueMandatory">Mandatory unique checkbox</label>
</div>
Comment on lines +669 to +675
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The “mandatory unique checkbox” is visually marked as required (.is-required) but the input is missing the required attribute. Either add required or adjust the example text so it’s not presented as mandatory.

Copilot uses AI. Check for mistakes.
</div>
</div>
<hr class="my-xlarge">
<fieldset class="control-items-list">
<legend class="is-required">Mandatory checkboxes</legend>
<div class="checkbox-item">
<div class="control-item-assets-container">
<input class="control-item-indicator" type="checkbox" value="" id="mandatoryCheckbox1" name="mandatoryCheckboxes" required />
</div>
<div class="control-item-text-container">
<label class="control-item-label" for="mandatoryCheckbox1">Default checkbox</label>
</div>
</div>
<div class="checkbox-item">
<div class="control-item-assets-container">
<input checked class="control-item-indicator" type="checkbox" value="" id="mandatoryCheckbox2" name="mandatoryCheckboxes" required />
</div>
<div class="control-item-text-container">
<label class="control-item-label" for="mandatoryCheckbox2">Checked checkbox</label>
</div>
</div>
</fieldset>`} />

14 changes: 13 additions & 1 deletion site/src/content/docs/components/password-input.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ A `placeholder` attribute is required on each `<input>` as our CSS-only floating
<p id="inputPasswordInvalidHelper" class="helper-text">Enter a password with at least 8 characters (without the DEV- prefix).</p>
<p id="inputPasswordInvalidError" class="error-text">Password must be at least 8 characters (without the DEV- prefix).</p>
</div>
<div class="text-input">
<div class="text-input mb-medium">
<div class="text-input-container text-input-container-outlined">
<svg aria-hidden="true">
<use xlink:href="${getVersionedDocsPath('/assets/img/ouds-web-sprite.svg#lock-closed')}"/>
Expand All @@ -142,4 +142,16 @@ A `placeholder` attribute is required on each `<input>` as our CSS-only floating
</div>
<p id="inputPasswordOutlinedInvalidHelper" class="helper-text">Enter a password with at least 8 characters (without the DEV- prefix).</p>
<p id="inputPasswordOutlinedInvalidError" class="error-text">Password must be at least 8 characters (without the DEV- prefix).</p>
</div>
<div class="text-input">
<div class="text-input-container">
<label for="madatoryPassword" class="is-required">Password</label>
<input type="password" id="madatoryPassword" class="text-input-field" placeholder=" " required>
Comment on lines +148 to +149
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in the example id/for attribute: madatoryPassword should be mandatoryPassword (or similar) to avoid propagating misspellings into copy-pasted code.

Suggested change
<label for="madatoryPassword" class="is-required">Password</label>
<input type="password" id="madatoryPassword" class="text-input-field" placeholder=" " required>
<label for="mandatoryPassword" class="is-required">Password</label>
<input type="password" id="mandatoryPassword" class="text-input-field" placeholder=" " required>

Copilot uses AI. Check for mistakes.
<button class="btn btn-minimal btn-icon">
Comment on lines +146 to +150
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR metadata links to #mandatory-field-indication, but this page doesn’t include a “## Mandatory field indication” heading/anchor (and doesn’t use the new <MandatoryFieldIndication /> shortcode like other form components). Consider adding the dedicated section at the end of the page so the anchor exists and the docs stay consistent.

Copilot uses AI. Check for mistakes.
<svg aria-hidden="true">
<use xlink:href="${getVersionedDocsPath('/assets/img/ouds-web-sprite.svg#accessibility-vision')}"/>
</svg>
<span class="visually-hidden">Show password</span>
</button>
</div>
</div>`} />
40 changes: 38 additions & 2 deletions site/src/content/docs/components/radio-button.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -330,9 +330,9 @@ You can align horizontally up to three radio buttons if their labels are short,

### Max width

By default radio buttons will span the whole width of their parent container, to limit the width of the radio button on wider parent container, add a `.component-max-width` to the `.radio-button-item` container. More information on radio button sizing in the [design guidelines](https://r.orange.fr/r/S-ouds-doc-radio-button-responsiveness).
By default, radio buttons will span the whole width of their parent container, to limit the width of the radio button on wider parent container, add a `.component-max-width` to the `.radio-button-item` container. More information on radio button sizing in the [design guidelines](https://r.orange.fr/r/S-ouds-doc-radio-button-responsiveness).

<Example code={`<fieldset class="control-items-list">
<Example buttonLabel="radio button max width" code={`<fieldset class="control-items-list">
<div class="radio-button-item component-max-width">
<div class="control-item-assets-container">
<input class="control-item-indicator" type="radio" aria-describedby="radioMWDescription" id="radioWithMaxWidth" value="" name="radioMaxWidth" />
Expand Down Expand Up @@ -604,3 +604,39 @@ For the standalone radio button, we provide a completely different architecture
<input class="form-check-input" type="radio" value="" aria-label="Default standalone radio button (Bootstrap compatible)" />
</div>`} />
</BootstrapCompatibility>


## Mandatory field indication

<MandatoryFieldIndication componentName="radio button" />

<Example buttonLabel="mandatory radio buttons indication" code={`<div class="radio-button-item-container">
<div class="radio-button-item">
<div class="control-item-assets-container">
<input class="control-item-indicator" type="radio" value="" id="radioUniqueMandatory" name="mandatoryRadio" />
</div>
<div class="control-item-text-container">
<label class="control-item-label is-required" for="radioUniqueMandatory">Mandatory unique radio button</label>
</div>
Comment on lines +614 to +620
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The “mandatory unique radio button” is visually marked as required (.is-required) but the input is missing the required attribute. Add required (typically on one radio in the group) or adjust the wording.

Copilot uses AI. Check for mistakes.
</div>
</div>
<hr class="my-xlarge">
<fieldset class="control-items-list">
<legend class="is-required">Mandatory radio buttons</legend>
<div class="radio-button-item">
<div class="control-item-assets-container">
<input class="control-item-indicator" type="radio" value="" id="mandatoryRadio1" name="mandatoryRadios" required />
</div>
<div class="control-item-text-container">
<label class="control-item-label" for="mandatoryRadio1">Default radio button</label>
</div>
</div>
<div class="radio-button-item">
<div class="control-item-assets-container">
<input checked class="control-item-indicator" type="radio" value="" id="mandatoryRadio2" name="mandatoryRadios" required />
</div>
<div class="control-item-text-container">
<label class="control-item-label" for="mandatoryRadio2">Checked radio button</label>
</div>
</div>
</fieldset>`} />
66 changes: 63 additions & 3 deletions site/src/content/docs/components/select-input.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ When there’s a value already defined, `<label>`s will automatically adjust to
</div>`} />

<BootstrapCompatibility>
<Example code={`<select class="form-select" aria-label="Default select example">
<Example buttonLabel="Bootstrap compatibility: selected select" code={`<select class="form-select" aria-label="Default select example">
<option selected>Open this select menu</option>
<option value="1">One</option>
<option value="2">Two</option>
Expand Down Expand Up @@ -218,9 +218,9 @@ To display a helper link below selects, use a standard small link [with `.link`

### Max width

By default select inputs will span the whole width of their parent container, to limit the width of the select input on wider parent container, add a `.component-max-width` to the `.select-input` container.
By default, select inputs will span the whole width of their parent container, to limit the width of the select input on wider parent container, add a `.component-max-width` to the `.select-input` container.

<Example code={`<div class="select-input component-max-width">
<Example buttonLabel="select input max width" code={`<div class="select-input component-max-width">
<div class="select-input-container">
<label for="exampleSelectMaxWidth">Select example max width</label>
<select class="select-input-field" id="exampleSelectMaxWidth" aria-describedby="selectMaxWidthHelper">
Expand Down Expand Up @@ -541,3 +541,63 @@ Group options with `<optgroup>`:
</select>
</div>
</div>`} />

## Mandatory field indication

<MandatoryFieldIndication componentName="select input" />

<Example buttonLabel="mandatory select input indication" code={`<div class="select-input">
<div class="select-input-container">
<label for="mandatorySelect" class="is-required">Mandatory select example</label>
<select class="select-input-field" id="mandatorySelect" required>
<option value="" disabled selected></option>
<option value="1">One</option>
<option value="2">Two</option>
<option value="3">Three</option>
</select>
</div>
</div>`} />

{/* TODO temporary examples for debug to remove */}
Examples to test some combinations

<Example buttonLabel="mandatory select input indication temp" code={`<div class="select-input mb-medium">
<div class="select-input-container">
<label for="mandatorySelect2" class="is-required">Mandatory select example Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer viverra nisi turpis, vel dignissim enim imperdiet eu. Ut quis mollis erat. Pellentesque id leo pellentesque urna volutpat placerat non nec sem.</label>
<select class="select-input-field" id="mandatorySelect2" required>
<option value="" disabled selected></option>
<option value="1">One</option>
<option value="2">Two</option>
<option value="3">Three</option>
</select>
</div>
</div>
<div class="select-input mb-medium">
<div class="select-input-container">
<label for="mandatorySelect3" class="is-required">Mandatory select example.</label>
<select class="select-input-field" id="mandatorySelect3" required aria-invalid="true">
<option value="" disabled selected></option>
<option value="1">One</option>
<option value="2">Two</option>
<option value="3">Three</option>
</select>
</div>
</div>
<div class="select-input">
<div class="select-input-container">
<svg aria-hidden="true">
<use xlink:href="/orange/docs/1.1/assets/img/ouds-web-sprite.svg#heart-empty"/>
</svg>
<label for="exampleDisabledLoadingSelect1" class="is-required">Disabled loading select example</label>
<select disabled class="select-input-field loading-indeterminate" id="exampleDisabledLoadingSelect1" aria-describedby="loading-msg-5">
<option value="" disabled></option>
<option value="1" selected>One</option>
<option value="2">Two</option>
<option value="3">Three</option>
</select>
<svg viewBox='0 0 40 40' xmlns='http://www.w3.org/2000/svg' class="loader" aria-hidden="true">
<circle class="loader-inner" cx="20" cy="20" r="17"></circle>
</svg>
<span role="status" id="loading-msg-5" class="visually-hidden d-none">Loading message</span>
</div>
</div>`} />
Loading
Loading