본문으로 바로가기
검색
Sign UpLogin

오늘자 PR: width 100%가 안 먹힌 이유, table 레이아웃에 대하여

오늘자 PR에 여담 좀 추가했다

fix: Tabs 및 자식 레이아웃 수정 by SihyeonHong · Pull Request #53 · SihyeonHong/EasiestCV


Bug Fix

번호 매긴 항목들은 모두 이어지는 내용.

`PublicHome` 모바일 레이아웃 약간 변경

기존 코드:

<CardContent className="flex flex-col items-start justify-center gap-5 md:flex-row">

items-center 였다가 md 이상에서 텍스트가 길 경우 이미지 상하에 여백 생기는 것을 고치기 위해 items-start 로 변경했었다.

그런데 모바일 화면에서는 items-start 하면 이미지가 작을 경우 왼쪽에 치우친다는 걸 까먹었다.

수정:

<CardContent className="flex flex-col items-center justify-center gap-5 md:flex-row md:items-start">

1. 모바일에서 내비게이션 바 반응형 안 되는 문제

부트스트랩 -> TailwindCSS 로 바꾸면서 Tabs 컴포넌트의 내비게이션 부분에 반응형 적용이 안 되어 있었다는 걸 잊고 있었다. (교훈: 모바일 우선 작업 제발)

폰에서 확인해보니 내비게이션 바 가로로 잘리고 있었다. flex-col 은 짧은 메뉴 여러개다 보니 안 어울릴 것 같아서, 가로 길이 넘치면 줄 넘어가는 걸로 결정했다.

shadcn에서 받은 Tabs 컴포넌트에서

const TabsList = React.forwardRef<
  (중략)
  className={cn(
    "inline-flex h-9"

inline-flex h-9 지우고 flex flex-wrap gap-1 추가.

  • inline-flex : 한 줄로만 되게 만드는 거

  • h-9 : 고정 높이 지워서 두 줄 이상 될 때 높이 자동 조절되도록

그리고 메뉴 좀 큰 게 더 예쁜 것 같아서 하는 김에 min-h-11 도 추가.

2. Tabs 너비 조절

1번 해결했더니 새롭게 등장한 문제: TabsList가 TabsContent 내부의 Card보다 약간 왼쪽으로 튀어나왔다.

이걸 해결하려고 뜯어보다 보니 Public과 Admin 페이지가 원칙상 레이아웃이 똑같아야 하는데, AdminTabsPublicContent에서 각자 레이아웃을 조절하고 있었다는 걸 발견했다.

통합하기 위해 그냥 Tabs 공통 컴포넌트에 margin과 width를 주기로 했다.

const Tabs = React.forwardRef<
  React.ElementRef<typeof TabsPrimitive.Root>,
  React.ComponentPropsWithoutRef<typeof TabsPrimitive.Root>
>(({ className, ...props }, ref) => (
  <TabsPrimitive.Root
    ref={ref}
    className={cn("mx-2 md:mx-8 lg:mx-auto lg:max-w-[1024px]", className)}
    {...props}
  />
));
Tabs.displayName = TabsPrimitive.Root.displayName;

const Tabs = TabsPrimitive.Root; 로 단순 alias 할당되어있던 Tabs를 forwardRef 로 래핑하여 TailwindCSS 유틸리티 클래스 추가.

기존에 PublicContent 등에 산발적으로 있던 <Card className="mx-2 md:mx-8 lg:w-[1024px]"> 와 같은 클래스는 모두 삭제.

아 맞다 로그인

로그인도 탭을 쓰는데 여긴 같은 너비가 아니라는 걸 깜빡했다.

하지만 그냥 Public과 Admin이 공동으로 사용하는 너비를 Tabs에 주고, 로그인 탭에만 예외 처리를 하는 게 낫겠다고 판단했다.

수정된 코드:

<div className="mx-auto flex w-full max-w-lg flex-col gap-4 p-2 md:max-w-xl">
  <Tabs defaultValue="login" className="mx-0 w-full max-w-none md:mx-0">

3. lg 미만 뷰포트에서 Card 사이즈 문제

2번에서 Tabs에 "mx-2 md:mx-8 lg:mx-auto lg:max-w-[1024px]" 를 추가했으나,

lg 이상의 화면에서는 1024px 고정된 너비로 잘 나왔지만 lg(1024px) 미만 뷰포트에서 의도대로 너비가 동작하지 않았다.

의도: mx-2 md:mx-8 여백만 남기고 Card 컴포넌트가 100% width로 채우기. 반응형.

실제: 내부 텍스트 양에 따라 너비가 조정됨. 따라서 탭을 바꿀 때마다 카드의 너비가 달라지는 현상(shrink-to-fit) 발생.

Admin에서는 너비가 꽉 채워지는데 Public에서만 문제가 발생해서 더 포인트를 찾기 어려웠다. 처음에는 둘 간 차이에 해답이 있을 거라고 생각해서 대조에 집중했으나 나중에 알고 보니 그냥 Admin 안에는 ReactQuill 에디터가 들어가 있어서 w-full이 가능했던 것뿐이었다.

3-1. PublicContainerw-full 주기

컴포넌트 구조가 대충 이런 식인데

1 <PublicContainer>
2   <PublicTabs> = <Tabs>
3     <PublicContents> = <Card>
4       <CardContent>
5         <div dangerouslySetInnerHTML={{

w-full부모 컴포넌트에서 줬어야 했는데 자식 컴포넌트에서 줘야 한다고 착각했다.

아무래도 Admin 쪽에서 ReactQuill 에디터로 인해 너비가 최대로 늘어날 수 있었다는 점을 파악한 다음이라 자식이 넓어져야 한다고 생각한 듯하다.

min-w-full, max-w-full, w-full 등 width 관련된 클래스들을 PublicTabs 이하의 컴포넌트들에서만 여러가지 조합으로 넣었다 빼기를 반복했다. prose도 width에 관여한다길래 prose max-w-none dark:prose-invert prose-headings:!text-inherit prose-p:!text-inherit 등의 클래스들도 마찬가지로 넣었다 뺐다 며칠간 삽질을 했다. Tailwind 클래스가 잘 안 먹히나 싶어서 인라인 스타일도 시도해봤지만 마찬가지였다.

w-full만을 위한 FullWidth라는 컴포넌트를 만들어서 역시 PublicTabs 이하에서 여기저기 끼워넣어 봤지만 소용없었다.

원래 PublicContainer는 CSS에 전혀 관여하지 않고 데이터 페칭 상태에 따라서 화면에 어떤 페이지를 띄워줄 것인지 결정하는 컴포넌트였기 때문에 거기까지 올라갈 생각을 미처 못했다. 여기까지 올라와서 w-full로 자식들을 제어해 줘야 하는 거였다.

수정한 코드: PublicContainer.tsx

<div className="flex w-full justify-center px-4">
  <div className="w-full max-w-[1024px]">

3-2. 또 다른 방법: display:table 활용

사실 이 방법을 먼저 알게 되었다.

export default function PublicContents({ userid, tid }: Props) {
  <Card className="table w-full table-fixed lg:w-[1024px]">
    <CardContent className="table-cell">
      {content && (
  • display: table: 요소를 테이블처럼 동작하게 만든다. 무슨 뜻이냐면, 해당 요소가 display: block처럼 쌓이는 대신 테이블처럼 레이아웃을 구성하며, 자식 요소 중 display: table-row, display: table-cell 등을 가진 요소들이 실제 테이블 구조처럼 정렬된다. 즉, 테이블의 레이아웃 규칙을 따르게 된다.

  • table-layout: fixed: 브라우저의 "최적 너비 계산"을 무시하고 테이블의 너비를 내용과 상관없이 고정.

  • display: table-cell: 자식 요소가 테이블 셀처럼 동작. 부모의 전체 너비를 차지.

그리고 Tabs에서 "w-full mx-2 md:mx-8 lg:mx-auto lg:max-w-[1024px]" 를 줘서

이렇게 지정된 너비를 강제로 유지하게 만들었다.

채택하지 않은 이유

table layout보다는 부모 컴포넌트에서 w-full 주는 것이 더 의도를 명확히 반영하기 때문에 3-1 방법을 선택했다.

여담

이 문제를 해결하다가 table layout이라는 것을 처음 알게 되고 AI한테 이게 어떤 원리로 내 문제를 해결해준 거냐고 물었는데,

이 문제는 CSS의 shrink-to-fit 동작 때문에 발생했습니다.

  • 블록 요소라도 특정 상황에서는 내용에 맞춰 너비가 축소됩니다

  • 브라우저가 텍스트 콘텐츠를 기반으로 "최적의 너비"를 계산하려고 시도합니다

  • width: 100%min-width: 100% 등은 여전히 이런 계산의 영향을 받습니다

HTML 요소의 width가 명시적으로 설정되지 않았거나, width: auto일 때 브라우저는 다음 중 최소 너비를 계산해서 요소의 실제 너비를 결정하게 됩니다.

  • min-content: 가장 긴 단어(또는 줄)를 줄바꿈 없이 담을 수 있는 최소 너비

  • max-content: 줄바꿈 없이 모든 내용을 한 줄에 담을 수 있는 최대 너비

  • available width: 부모 요소에서 사용할 수 있는 남은 공간

(예시 코드)

  • w-full이라도 부모 컨텍스트가 제대로 정의되지 않았거나, 내부에 길쭉한 텍스트가 있으면

  • 브라우저는 “이 내용을 다 보여주려면 이만큼 너비가 있어야겠네” → Card 자체를 넓혀버림

이런 설명을 해주길래 여기서 깨달았다.

"그러니까 나는 지금까지 주로 실제 콘텐츠가 담기는 html div랑 그 근처 Card에서 주로 width를 조절하려고 시도했는데, 그보다는 부모 컴포넌트에서 width를 조절했어야 했던 거야?" 라고 물으니 맞단다....

Others

  • InitPage에서 UsageGuide 컴포넌트 분리: 아래 데모 계정 설명 부분. InitPage 여전히 파일 너무 길어서 나중에 차차 컴포넌트 분리하고 싶음.

  • 불필요한 Tailwind Class 삭제. (ex: 안 먹히고 있던 w-full, p-0 등)

  • 오타 수정.