components icon indicating copy to clipboard operation
components copied to clipboard

feat(material/form-field): add MatLabel input property to change use of html <label>

Open jpduckwo opened this issue 1 year ago • 11 comments

Feature Description

Add an input property to MatLabel that changes the use of <label> into a basic div element without linking it to a control. Perhaps it could be implemented as:

<mat-form-field>
  <mat-label [displayOnly]="true">The label</mat-label>
  <non-labelable-form-control/>
</mat-form-field>

Use Case

When using form fields with standard 'labelable' form elements such as <input> and <select> and using a <mat-label> to label the field, the HTML specification for the <label for="x"> works very nicely. However, there are times when non-standard form elements are desirable to use, such as <mat-select> with html formatted options, or other custom form fields that you might build yourself. In these cases when you use the <mat-label> element, the resulting <label for="x"> generates a page error with the violation:

Incorrect use of <label for=FORM_ELEMENT>

image

You can leave out <mat-label> to work around this, however, it is a better user experience to have the floating label and to also have these form fields match the appearance and behaviour of the other fields in a form.

jpduckwo avatar Jun 06 '23 00:06 jpduckwo

In the screenshot you show it looks like the label is associated with an invalid id, which seems like a separate bug. Angular Material allows custom form controls to specify their id and components like mat-select do this. However, it is a valid point that the for attribute is supposed to link to a "labelable element", and mat-select doesn't qualify as labelable according the html spec. We should look into an alternate way to make the association in these cases, or figure out if we can make the mat-select and other custom elements labelable (the spec mentions that form-associated custom elements are labelable)

mmalerba avatar Jun 12 '23 21:06 mmalerba

Thanks @mmalerba for the input. Interestingly, you can see the same error as per my screenshot in the error generated on the mat-select example page. However, I'm not able to replicate it on a stackblitz. here is my stackblitz.

Is there something else at play here?

jpduckwo avatar Jun 13 '23 06:06 jpduckwo

This bug is present everywhere, not only in your case. It's even on the stackblitz examples of the select component guide: https://material.angular.io/components/select/overview

When you open the first example in stackblitz, the console shows:

Incorrect use of <label for=FORM_ELEMENT>
The label's for attribute doesn't match any element id. This might prevent the browser from correctly autofilling the form and accessibility tools from working correctly.
To fix this issue, make sure the label's for attribute references the correct id of a form field.

Sergiobop avatar Jun 15 '23 09:06 Sergiobop

I'm thinking actually this is an issue with Chrome detecting this problem. @mmalerba is right that the error message is stating that there is no element with the id in the for="" element. This isn't right, as the mat-select has the correct matching id attribute. I believe Chrome may be picking up this error due to the way the element is created - maybe it picks up the error in the middle of the process of initialisation of the field - or it's a bug with how Chrome detects the error. I don't believe it would be classified as an error if the element was matching but non-labelable. I'm going to leave this feature open for the moment, but leaning towards this request not being the way to solve this.

jpduckwo avatar Jun 15 '23 23:06 jpduckwo

This issue appears for every mat-select. The reason being that the generated <label for=""> points to a non-existing element, and there isn't a <select> being generated by using a mat-select in the first place. I hope this gets looked into soon because I now have to go through all kinds of hoops to generate a flat <span> or <div> that looks and behaves the same as a mat-label.

LeroyGerrits avatar Sep 02 '23 12:09 LeroyGerrits

Any updates on this ?

todorpavlovic avatar Nov 24 '23 08:11 todorpavlovic

I encountered the same issue with mat-label and mat-date-range-input on Brave browser

image

anderer455 avatar Jan 15 '24 17:01 anderer455

By adding a hidden <select> element, this error can be avoided. Temporary solutions, informal. Hope this issue can be properly resolved.

<mat-form-field>
  <mat-label>Favorite Food</mat-label>
  <select id="food-select" style="display: none;"></select>  <!-- add this line -->
  <mat-select id="food-select" [(value)]="selectedFood">  <!-- add the same `id` attribute as above -->
    <mat-option value="apple">apple</mat-option>
    <mat-option value="banana">banana</mat-option>
  </mat-select>
</mat-form-field>

jeanfliu avatar Feb 02 '24 02:02 jeanfliu

By adding a hidden <select> element, this error can be avoided. Temporary solutions, informal. Hope this issue can be properly resolved.

<mat-form-field>
  <mat-label>Favorite Food</mat-label>
  <select id="food-select" style="display: none;"></select>  <!-- add this line -->
  <mat-select id="food-select" [(value)]="selectedFood">  <!-- add the same `id` attribute as above -->
    <mat-option value="apple">apple</mat-option>
    <mat-option value="banana">banana</mat-option>
  </mat-select>
</mat-form-field>

Personally I feel it's a bad idea. Your making a label for to any not visible, why not the solution proposed in the SO?

@Directive({
  selector: 'mat-select',
})
export class RemoveFor implements AfterViewInit {
  constructor(private el:ElementRef){}
  ngAfterViewInit()
  {
    setTimeout(()=>{
      const label=this.el.nativeElement.parentNode.getElementsByTagName('label')
      if (label.length && label[0].getAttribute('for'))
          label[0].removeAttribute('for')
    })
  }
}

When the problem is solved, simply we'll remove the directive from all the imports places

EliseoPlunker avatar Feb 02 '24 08:02 EliseoPlunker

By adding a hidden <select> element, this error can be avoided. Temporary solutions, informal. Hope this issue can be properly resolved.

<mat-form-field>
  <mat-label>Favorite Food</mat-label>
  <select id="food-select" style="display: none;"></select>  <!-- add this line -->
  <mat-select id="food-select" [(value)]="selectedFood">  <!-- add the same `id` attribute as above -->
    <mat-option value="apple">apple</mat-option>
    <mat-option value="banana">banana</mat-option>
  </mat-select>
</mat-form-field>

Personally I feel it's a bad idea. Your making a label for to any not visible, why not the solution proposed in the SO?

@Directive({
  selector: 'mat-select',
})
export class RemoveFor implements AfterViewInit {
  constructor(private el:ElementRef){}
  ngAfterViewInit()
  {
    setTimeout(()=>{
      const label=this.el.nativeElement.parentNode.getElementsByTagName('label')
      if (label.length && label[0].getAttribute('for'))
          label[0].removeAttribute('for')
    })
  }
}

When the problem is solved, simply we'll remove the directive from all the imports places

I mean yeah this is the solution that works but it works only now. It wont work once it gets fixed. The thing is I don't want to remember that I will have to delete this someday actually I'm sure I won't remember. I will forget it 100%. And to be even able to remove the directive in future I have to keep an eye here to actually know when it gets fixed. Hate to see the error but only logical thing for me is to leave it as is. Once it gets fixed it should just go away. Right? Right?

anderer455 avatar Feb 02 '24 10:02 anderer455

Hello folks,

Thank you for reporting this. We prioritize accessibility issues that are a problem for users. If you have an explaination why this does not meet an a11y criteria (such as WCAG), we can consider that.

Angular components doesn't have a hard requirement to cleanly pass automated accessibility checks.

The reported issue in Chrome may be a false positive. Are you having accessibility or autocomplete issues in your application?

Best Regards,

Zach

zarend avatar Feb 02 '24 16:02 zarend