본문으로 건너뛰기
Amineslab UI

Component

FormSurface

페이지형 폼을 PC 카드와 모바일 full-bleed + 고정 footer로 표현합니다.

Playground

옵션을 조절하며 화면과 사용 코드를 함께 확인하세요.

PC 1280px
Controls

Footer: 모바일 화면 하단 고정 / 본문 흐름

PC 카드 표면 · 모바일은 평평한 배경

PC 본문과 footer 여백 · 모바일 본문은 0

FormSurfaceFooter 표시

긴 폼에서 스크롤과 마지막 필드 가림 확인

Code
import { useState } from 'react'

import { FormSurface, FormSurfaceContent, FormStack, TextField, NumberField, TextareaField, FormSurfaceFooter, FormActions } from '@amineslab/ui'

function FormLayoutExample({ compact = false, gap = 'md', columns = '1', }: {
    compact?: boolean;
    gap?: string;
    columns?: string;
}) {
    return (<FormStack gap={compact ? 'sm' : gap === 'sm' || gap === 'lg' ? gap : 'md'} columns={columns === '2' ? 2 : 1}>
      <TextField label="프로젝트 이름" placeholder="Amineslab UI" required description="팀에서 식별할 이름입니다."/>
      <NumberField label="예산" placeholder="금액을 입력해 주세요"/>
      <TextareaField label="설명" placeholder="프로젝트를 소개해 주세요" rows={3}/>
    </FormStack>);
}

function FormSurfaceExample({ footer, fixedOnMobile = true, variant = 'soft', padding = 'md', longContent = false, }: {
    footer: boolean;
    fixedOnMobile?: boolean;
    variant?: string;
    padding?: string;
    longContent?: boolean;
}) {
    const [saved, setSaved] = useState(false);
    return (<form className="w-full" onSubmit={(event) => {
            event.preventDefault();
            setSaved(true);
        }}>
      <FormSurface variant={variant === 'raised' || variant === 'flat' || variant === 'muted' ? variant : 'soft'} padding={padding === 'none' || padding === 'sm' || padding === 'lg' ? padding : 'md'}>
        <FormSurfaceContent>
          <FormStack gap="lg">
            <FormLayoutExample compact={false}/>
            {longContent && (<>
                <TextareaField rows={12} label="운영 정책" placeholder="운영 정책을 입력해 주세요"/>
                <TextareaField label="마지막 메모" rows={12} placeholder="하단 액션에 가려지지 않는지 확인해 주세요"/>
              </>)}
          </FormStack>
        </FormSurfaceContent>
        {footer && (<FormSurfaceFooter fixedOnMobile={fixedOnMobile}>
            <FormActions cancelOptions={{ onClick: () => setSaved(false) }} submitOptions={{ children: saved ? '저장됨' : '저장' }}/>
          </FormSurfaceFooter>)}
      </FormSurface>
    </form>);
}

export default function ExamplePreview() {
  return (<FormSurfaceExample {...({"footer":true,"fixedOnMobile":true,"variant":"soft","padding":"md","longContent":false} as const)}/>)
}

Props

import {
  FormSurface,
  FormSurfaceContent,
  FormSurfaceFooter,
} from '@amineslab/ui'

FormSurface

Card 표면을 화면 폭에 맞춰 조정합니다.

NameTypeDefaultDescription
variant'raised' | 'flat' | 'muted' | 'soft''soft'PC 카드 배경·테두리·그림자입니다. 모바일은 평평한 표면을 사용합니다.
padding'none' | 'sm' | 'md' | 'lg''md'PC 본문 여백과 footer 좌우 여백을 함께 변경합니다. 모바일 본문 여백은 0입니다.
classNamestring-공개 surface wrapper 클래스입니다.

FormSurfaceContent

폼 본문 padding을 PC·모바일 규칙으로 관리합니다.

NameTypeDefaultDescription
children*ReactNode-스크롤 대상 폼 본문입니다.

FormSurfaceFooter

모바일 하단 액션과 safe area를 관리하며 높이만큼 자리를 확보합니다.

NameTypeDefaultDescription
fixedOnMobilebooleantrue모바일에서 화면 하단 고정입니다. false면 본문 흐름에 배치합니다. PC는 항상 카드 내부입니다.
children*ReactNode-FormActions 같은 액션 콘텐츠입니다.
classNamestring-공개 footer wrapper 클래스입니다.

Examples

자주 쓰는 조합을 살펴보고, 필요한 예시의 코드를 펼쳐 확인하세요.

페이지 폼 표면

PC에서는 카드 안의 footer, 모바일에서는 safe-area를 포함한 고정 footer로 바뀝니다.

팀에서 식별할 이름입니다.

import { useState } from 'react'

import { FormSurface, FormSurfaceContent, FormStack, TextField, NumberField, TextareaField, FormSurfaceFooter, FormActions } from '@amineslab/ui'

function FormLayoutExample({ compact = false, gap = 'md', columns = '1', }: {
    compact?: boolean;
    gap?: string;
    columns?: string;
}) {
    return (<FormStack gap={compact ? 'sm' : gap === 'sm' || gap === 'lg' ? gap : 'md'} columns={columns === '2' ? 2 : 1}>
      <TextField label="프로젝트 이름" placeholder="Amineslab UI" required description="팀에서 식별할 이름입니다."/>
      <NumberField label="예산" placeholder="금액을 입력해 주세요"/>
      <TextareaField label="설명" placeholder="프로젝트를 소개해 주세요" rows={3}/>
    </FormStack>);
}

function FormSurfaceExample({ footer, fixedOnMobile = true, variant = 'soft', padding = 'md', longContent = false, }: {
    footer: boolean;
    fixedOnMobile?: boolean;
    variant?: string;
    padding?: string;
    longContent?: boolean;
}) {
    const [saved, setSaved] = useState(false);
    return (<form className="w-full" onSubmit={(event) => {
            event.preventDefault();
            setSaved(true);
        }}>
      <FormSurface variant={variant === 'raised' || variant === 'flat' || variant === 'muted' ? variant : 'soft'} padding={padding === 'none' || padding === 'sm' || padding === 'lg' ? padding : 'md'}>
        <FormSurfaceContent>
          <FormStack gap="lg">
            <FormLayoutExample compact={false}/>
            {longContent && (<>
                <TextareaField rows={12} label="운영 정책" placeholder="운영 정책을 입력해 주세요"/>
                <TextareaField label="마지막 메모" rows={12} placeholder="하단 액션에 가려지지 않는지 확인해 주세요"/>
              </>)}
          </FormStack>
        </FormSurfaceContent>
        {footer && (<FormSurfaceFooter fixedOnMobile={fixedOnMobile}>
            <FormActions cancelOptions={{ onClick: () => setSaved(false) }} submitOptions={{ children: saved ? '저장됨' : '저장' }}/>
          </FormSurfaceFooter>)}
      </FormSurface>
    </form>);
}

export default function ExamplePreview() {
  return (<FormSurfaceExample footer/>)
}

사용 기준

권장하는 사용
  • FormSurface는 페이지 폼의 바깥 표면입니다.
  • PC는 카드와 내부 여백, 모바일은 평평한 본문과 하단 액션을 제공합니다.
  • Footer가 자기 높이만큼 본문 자리를 확보하므로 특정 main 구조가 필요하지 않습니다.
  • native form으로 감싸 제출을 연결하세요.
  • Dialog·Drawer에서는 해당 컴포넌트의 Footer를 사용합니다.
피해야 할 사용
  • 겉모양만으로 사용을 결정하지 마세요.
  • 의미와 키보드 동작, 모바일에서의 흐름을 함께 확인하세요.

접근성

화면에 맞는 이름을 제공하고, 키보드만으로 작업을 마칠 수 있는지 확인하세요.

  • Tab: 본문과 고정 footer 사이에서 순서가 끊기지 않습니다.