feat: add readOnlyVariant to TextField and TextArea#1059
Conversation
TextField and TextArea hardcoded a grey read-only fill with no opt-out.
Add a readOnlyVariant prop ('filled' | 'plain', default 'filled') so
consumers can render a read-only field without the fill. PasswordField
inherits it via TextField. The default preserves existing behaviour.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@doistbot /review |
doistbot
left a comment
There was a problem hiding this comment.
Thanks Luis for cleanly implementing the new readOnlyVariant prop to give us more visual flexibility with read-only fields 😎 👊.
Few things worth tightening:
- Ensure the new tests for both
TextFieldandTextAreaverify that the components remain functionally non-editable whenreadOnlyVariant="plain"is passed, rather than just asserting on the styling classes.
Address Doistbot review on #1059: the plain-variant cases only checked the styling class. Also assert typing does not change the value so a regression dropping readOnly on the plain path can't pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
doistbot
left a comment
There was a problem hiding this comment.
Thank you Luis for the clean, well-tested addition of the readOnlyVariant to our text components 🤗.
No issues were flagged during the review.
I also included a few optional follow-up notes in the details below.
Optional follow-up note (1)
- [P3] src/text-area/text-area.module.css:32: For
TextArea, switching the default filled style fromtextarea[readonly]to a.readOnlyclass duplicates the same state in JS and CSS. The filled case can stay anchored to the nativereadonlyattribute, andreadOnlyVariant="plain"can add a small opt-out class/attribute instead. That keeps the visual default tied to the semantic readonly state and removes one sync point to maintain.
|
@engfragui tagging you on this one for review – can't recall which user/group you recommended on the last meeting 😅 Feel free to re-assign. |
|
@lmjabreu see the linked handbook docs in the template: https://handbook.doist.com/doc/code-reviews-THNOSl1QID#h-15-assign-a-random-reviewer (it's |
|
I'm still not sure if I understand why we need two variants for read-only mode. Why wouldn't we want consistency instead? That is, that read-only input fields always show consistently in the same way no matter what. I think we should not have the gray background variant at all, even if it's the default today. Graying out the field is more appropriate for disabled fields. What do you think? |
We don't! :D The plain variant only existed because of Goals and the old Project Descriptions sidebars (both being dropped, but they still exist in code today). The use-case for the plain variant is better served by a separate component (EditableText like Paweł says). FWIW, EditableText has a lot of use-cases across TD: project name, section name/description, task name/description, and so on. Basically any text that can be edited inline, but it's usually styled as display text (so.. Regarding the grey 'disabled' styling: agree, that's addressed in subsequent PRs. TLDR: one read-only style for inputs, and one editable-text component for editable (display/UI) text. |
|
@lmjabreu if I read your comment correctly, then we can close this PR without merging, right? |

No linked issue. Component-library follow-up from building read-only description views in todoist-web.
Short description
TextFieldandTextAreahardcode a grey fill on their read-only state, with no way to opt out. Consumers that want a read-only field to read as plain text override the fill with their own CSS.This adds a
readOnlyVariantprop to both (also inherited byPasswordField):filled(default) keeps today's grey fill, so existing usage is unchanged.plainsuppresses the fill, so a read-only field matches its editable appearance.The prop only applies when
readOnlyis set.TextArea's CSS moves from thetextarea[readonly]selector to a.readOnlyclass so the fill can be gated on the variant; the default render is identical to before. The shared types live inbase-field.This is the first of three styling-only PRs for the read-only state (#1060 subdues the border and drops hover; #1061 fixes the bordered fill). None of them removes part of the component.
On the two variants
Review asked whether we need two (discussion). Long term, no: the goal is one read-only style for genuine inputs (a copyable token, a feed URL), with the no-fill case moving to a separate
EditableTextcomponent for inline-editable display text.plainstays for now because two todoist-web surfaces still render that way (goal-edit-view,project-details-sidebar). Both are being removed but exist in code today, so dropping the variant now would regress them. The prop replaces their CSS overrides in the meantime. Removingplainis a separate PR once those surfaces are gone.PR Checklist