Skip to content

fix(checkbox): rename internal id field to avoid createElement error - #4191

Open
adamczykpiotr wants to merge 1 commit into
mainfrom
fix/checkbox-createelement-id-collision
Open

fix(checkbox): rename internal id field to avoid createElement error#4191
adamczykpiotr wants to merge 1 commit into
mainfrom
fix/checkbox-createelement-id-collision

Conversation

@adamczykpiotr

@adamczykpiotr adamczykpiotr commented Jul 27, 2026

Copy link
Copy Markdown

Summary by CodeRabbit

  • Bug Fixes
    • Prevented automatically generated IDs from being added to the checkbox host element.
    • Preserved consumer-provided checkbox IDs.
    • Maintained correct association between the checkbox input and its label for accessibility.

Fixes #4190

What

limel-checkbox held a private field literally named id:

private id: string = createRandomString();

Because the component extends HTMLElement, id shadows the native
HTMLElement.prototype.id accessor. Compiled with the default
useDefineForClassFields: false, the field initializer runs in the
constructor as this.id = …, invoking the native id setter, which adds an
id attribute to the element during construction.

Per the DOM "create an element" algorithm,
an already-defined custom element constructed synchronously
(document.createElement/createElementNS) must be pristine — no attributes,
no children — or the browser throws
NotSupportedError: The result must not have attributes
(HTML custom-element requirement).

Frameworks that construct elements eagerly hit this. Vue's renderer calls
document.createElement('limel-checkbox') on the already-registered tag, so it
upgrades synchronously, the constructor adds id, and it throws — the checkbox
never hydrates (no shadow root, no hydrated class). Sibling components
(limel-button, limel-input-field, …) don't assign this.id, so they're
unaffected. The Stencil lazy-load / HTML-parser path never trips the check,
which is why this wasn't caught before.

Fix

Rename the field to inputId (consistent with the sibling helperTextId) so it
no longer shadows the native property, and update its read in render().
checkbox.template.tsx is unchanged — its id prop is a key on a plain props
object, not on an HTMLElement.

No behaviour change to the internal <input>/<label> wiring. Bonus: the host
element no longer gets a stray generated id, and a consumer-set
<limel-checkbox id="…"> is now left untouched (previously the constructor
overwrote it).

Tests

Added spec coverage in checkbox.spec.tsx:

  • host element is not given a generated id (the regression);
  • a consumer-provided host id is preserved;
  • the internal input still gets an id and the label's for still matches it.

Note: I couldn't run the suite locally (fresh clone without installed
dependencies) — relying on CI to validate.

Related (not in this PR)

The same private id pattern exists in dialog/dialog.tsx, but there it's
assigned in componentWillLoad() rather than a field initializer, so it dodges
the crash — though it still stamps a generated id onto the host. Worth the
same rename as a follow-up. radio-button-group/radio-button.tsx exposes a
deliberate @Prop() id (Stencil accessors shadow the native setter; no
constructor assignment) and is not affected.

The component held a private field literally named `id`
(`private id = createRandomString()`). Because the component extends
HTMLElement, `id` shadows the native `HTMLElement.prototype.id` accessor.
Compiled with `useDefineForClassFields: false`, the field initializer runs
in the constructor as `this.id = …`, invoking the native `id` setter, which
adds an `id` attribute to the element during construction.

Per the DOM spec, a custom element constructed synchronously via
`document.createElement()`/`createElementNS()` must be pristine (no
attributes/children); otherwise the browser throws
`NotSupportedError: The result must not have attributes`. Frameworks that
construct elements eagerly hit this — e.g. Vue's renderer calls
`document.createElement('limel-checkbox')` on the already-defined tag, so it
upgrades synchronously, the constructor adds `id`, and it throws. The checkbox
then never hydrates. The Stencil lazy-load / parser path is unaffected, which
is why this wasn't caught before.

Rename the field to `inputId` (consistent with the sibling `helperTextId`) so
it no longer shadows the native property. Behaviour of the internal
input/label wiring is unchanged; the host element no longer gets a stray
generated `id`, and a consumer-provided host `id` is now left untouched.

Fixes #4190

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 64656ac5-6bf6-41dd-aba0-b4f4f199cabc

📥 Commits

Reviewing files that changed from the base of the PR and between fdeccb9 and a6a1d00.

📒 Files selected for processing (2)
  • src/components/checkbox/checkbox.spec.tsx
  • src/components/checkbox/checkbox.tsx

📝 Walkthrough

Walkthrough

The checkbox’s generated input identifier is renamed to avoid shadowing the native host id property. Regression tests cover absent and consumer-provided host ids, plus input-to-label association.

Changes

Checkbox id handling

Layer / File(s) Summary
Separate internal input id from host id
src/components/checkbox/checkbox.tsx, src/components/checkbox/checkbox.spec.tsx
The internal identifier is renamed to inputId, template wiring uses it for the checkbox input, and tests verify host id preservation and label association.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested labels: maintenance

Suggested reviewers: lucychyzhova

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: renaming the checkbox's internal id field to avoid the createElement error.
Linked Issues check ✅ Passed The rename and render update address the NotSupportedError root cause, while tests confirm host id behavior and preserved input/label wiring.
Out of Scope Changes check ✅ Passed The added tests and field rename are directly related to the reported checkbox construction bug.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/checkbox-createelement-id-collision

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown

Documentation has been published to https://lundalogik.github.io/lime-elements/versions/PR-4191/

@adamczykpiotr adamczykpiotr self-assigned this Jul 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Broken limel-checkbox when not using component lazy-loading

1 participant