Skip to content

UI v0.19.2 Field접근성개선 - #36

Merged
youngduck merged 3 commits into
mainfrom
ui-v0.19.2-Field접근성개선
Jul 14, 2026

Hidden character warning

The head ref may contain hidden characters: "ui-v0.19.2-Field\uc811\uadfc\uc131\uac1c\uc120"
Merged

UI v0.19.2 Field접근성개선#36
youngduck merged 3 commits into
mainfrom
ui-v0.19.2-Field접근성개선

Conversation

@youngduck

Copy link
Copy Markdown
Owner

개요

PR #35 리뷰(gemini-code-assist)에서 지적된 Field 컴포넌트 접근성/웹표준 이슈를 반영합니다. (v0.19.1 머지 후 도착한 리뷰라 별도 PR로 분리)

변경 내용

  • <label> 전체 래핑 → <div> 컨테이너 + <label htmlFor> 연결 구조로 변경
    • label 안에 블록 요소(DatePicker/SelectBox 의 div)를 넣는 HTML 표준 위반 해소
    • label 내부 클릭 시 강제 포커스 이동으로 커스텀 드롭다운이 오동작할 여지 제거
  • 단일 입력 children 에 id / aria-describedby / aria-invalid 자동 주입
    • 스크린 리더가 입력 포커스 시 설명·에러 메시지를 함께 읽음
    • 자식이 자체 id 를 가진 경우 기존 id 로 라벨 연결 (리뷰 제안 대비 보강)
  • Field Props 타입: label 속성 → div 속성으로 변경, 문서/스토리 설명 갱신

🤖 Generated with Claude Code

youngduck and others added 3 commits July 14, 2026 17:56
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 14, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
yd-ui Ready Ready Preview, Comment Jul 14, 2026 8:59am

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request improves the accessibility of the Field component by changing its wrapper from a label to a div and automatically injecting id, aria-describedby, and aria-invalid attributes into a single child element while linking the label via htmlFor. The review feedback suggests adding defense code to handle React.Fragment children, preserving existing aria attributes on the child element instead of overwriting them, and considering how custom components pass down these injected attributes.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

*/

import { useId } from 'react'
import { useId, Children, isValidElement, cloneElement } from 'react'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

React.Fragment가 children으로 전달될 때의 런타임 경고를 방지하기 위해 Fragment 타입을 체크할 수 있도록 Fragment를 import 목록에 추가합니다.

Suggested change
import { useId, Children, isValidElement, cloneElement } from 'react'
import { useId, Children, isValidElement, cloneElement, Fragment } from 'react'

Comment on lines +38 to +46
const child = Children.only(children)
if (isValidElement<InjectedInputProps>(child)) {
inputId = child.props.id ?? inputId
content = cloneElement(child, {
id: inputId,
'aria-describedby': hasMessage ? messageId : undefined,
'aria-invalid': error ? true : undefined,
})
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

개선 제안 및 고려사항

  1. Fragment 방어 코드 추가: children이 단일 요소이지만 <React.Fragment>인 경우, cloneElement로 HTML 속성(id, aria-*)을 주입하면 런타임 경고가 발생합니다. child.type !== Fragment 조건을 추가하여 이를 방지하는 것이 안전합니다.
  2. 기존 aria 속성 보존: 자식 요소가 이미 자체적으로 aria-describedbyaria-invalid 속성을 가지고 있을 수 있습니다. 현재 방식은 이를 완전히 덮어쓰므로, 기존 속성이 있다면 이를 유지하거나 병합(aria-describedby인 경우 공백으로 구분하여 결합)하도록 개선하는 것이 좋습니다.
  3. 커스텀 컴포넌트 전달 한계 (구조적 고려사항): DatePicker처럼 내부 대화형 요소(예: <button>)를 <div> 컨테이너로 감싸고 ...props를 컨테이너에 스프레드하는 컴포넌트의 경우, idaria-* 속성이 실제 버튼이 아닌 최상위 <div>에 부여됩니다. 이로 인해 라벨 클릭 시 포커스가 가지 않거나 스크린 리더 접근성이 깨질 수 있습니다. 향후 DatePicker 등 커스텀 입력 컴포넌트들이 idaria-* 속성을 내부 대화형 요소로 전달하도록 수정하는 작업을 함께 검토해 주세요.
    const child = Children.only(children)
    if (isValidElement<InjectedInputProps>(child) && child.type !== Fragment) {
      inputId = child.props.id ?? inputId
      const existingDescribedBy = child.props['aria-describedby']
      content = cloneElement(child, {
        id: inputId,
        'aria-describedby': hasMessage
          ? (existingDescribedBy ? `${existingDescribedBy} ${messageId}` : messageId)
          : existingDescribedBy,
        'aria-invalid': error ? true : child.props['aria-invalid'],
      })
    }

@youngduck
youngduck merged commit 0b01e9c into main Jul 14, 2026
5 of 6 checks passed
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.

1 participant