서페이스 스타일 | CoUI
LogoCoUI

서페이스 스타일

표면 재질 축 — 켜는 법, 무엇이 달라지는지, 어디까지 닿는지

서페이스 스타일#

앱 전체의 표면이 어떤 재질로 보이는지를 한 번에 정하는 축입니다. 컴포넌트를 하나도 고치지 않고 켭니다.

다섯 가지가 있습니다 — Default · Neo-brutalism · Liquid Glass · Neumorphism · Claymorphism. 이 문서 상단의 스위처로 지금 바로 바꿔 보실 수 있습니다.

켜는 법#

읽는 쪽은 양 플랫폼이 같습니다 — SurfaceStyleScope.of(context). 정하는 쪽이 다릅니다.

// Flutter — 앱 루트가 필드로 받습니다
CoUIApp(
  surfaceStyle: CoreSurfaceStyleId.liquidGlass,
  theme: appTheme,
  home: const HomePage(),
)
// Web — 앱 루트는 CoUIWeb 이고, 스타일은 스코프의 정적 메서드로 정합니다
SurfaceStyleScope.setSurfaceStyle(CoreSurfaceStyleId.liquidGlass);

CoUIWeb(
  theme: ThemeData.coui,
  child: MyHomePage(),
)

Web 은 시트에 스타일 블록이 들어 있어야 합니다. 규칙이 참조할 변수가 없으면 아무것도 그려지지 않습니다:

final config = ThemeConfig.defaults();
// <style> 안에 config.surfaceStyleVariableBlocks 를 함께 넣습니다.

CoUIWeb 에는 CoUIAppsurfaceStyle 에 해당하는 생성자 필드가 없습니다. 이 문서는 그 사실을 기록만 하고 정당화하지 않습니다 — 한쪽에만 있는 파라미터는 이 킷의 기준으로 미구현 신호이고, 그 판정은 별도로 추적합니다.

data-coui-style 을 직접 쓰지 마세요#

Web 에서 축은 문서 속성으로 전달되지만, 그 속성을 손으로 쓰는 것은 공개 API 를 우회하는 저수준 경로입니다. setSurfaceStyle 은 기본 스타일일 때 값을 쓰는 대신 속성을 지웁니다 — 기본 규칙이 이미 그 상태를 기술하므로, 속성을 남기면 "스타일 없음" 이 두 가지 표기를 갖게 됩니다.

속성을 직접 쓰는 자리는 이 사이트에 딱 하나 있습니다. 첫 페인트 전에 저장된 선택을 복원하는 인라인 스크립트이고, 그건 사이트 인프라이지 소비 앱이 하는 배선이 아닙니다.

헤더의 스위처는 이 사이트의 데모입니다#

매 페이지 상단에 있는 스타일 스위처는 문서 사이트가 자기 축을 보여주려고 만든 것이고, 킷은 최종 사용자용 스타일 피커를 출하하지 않습니다.

런타임 전환이 불가능하다는 뜻은 아닙니다 — setSurfaceStyle 이 바로 그 런타임 API 입니다. 앱이 사용자에게 스타일 선택을 노출하고 싶다면 그 UI 는 앱이 만들고, 이 축이 그것을 받습니다.

무엇이 달라지는가#

축은 자기를 표면이라고 표시한 곳에만 닿습니다. "전부 바뀐다" 는 참이 아니고, 참이 아닌 문장은 안 바뀐 컴포넌트를 발견했을 때 버그로 읽힙니다.

여덟 개입니다:

OutlinedContainer · Card · Popup · Dialog · Tooltip · Dock · NavigationBar · Scaffold

CardOutlinedContainer 를 합성하므로 그것을 통해 받습니다. Scaffold앱 바만 받습니다 — 페이지 배경은 가장 바깥 채움이라 뒤에 아무것도 없고, 푸터는 콘텐츠 위에 뜨지 않습니다.

나머지 컴포넌트는 이 표면들 안에 놓이거나, 다시 칠해진 시맨틱 토큰을 상속받아 따라옵니다.

Liquid Glass 가 실제로 하는 일#

세 겹입니다.

무엇
반투명 채움표면이 뒤를 비칩니다
배경 블러 + 채도비쳐 보이는 것을 흐리고 색을 살짝 올립니다
엣지 시인가장자리를 훑는 얇은 띠. 가로 변과 세로 변의 무게가 다릅니다

반투명과 블러는 짝입니다. 불투명한 채움 뒤의 블러는 보이지 않고, 블러 없는 반투명은 그냥 옅은 표면입니다. 둘은 항상 함께 움직입니다.

블러는 뒤에 무언가 있을 때만 보입니다. 평평한 단색 위에 놓인 유리 표면은 흐릴 것이 없어 유리처럼 보이지 않습니다 — 결함이 아니라 재질의 성질입니다.

앱이 정할 수 있는 것#

프리셋은 기본 답이지 유일한 답이 아닙니다. CoreSurfaceTuning 으로 값을 진술하면 되고, 말하지 않은 값은 프리셋 것이 그대로 남습니다.

// Flutter — 활성 스타일이 한 번에 하나라 단수
CoUIApp(
  surfaceStyle: CoreSurfaceStyleId.liquidGlass,
  surfaceTuning: const CoreSurfaceTuning(
    surfaceOpacity: 0.5,
    surfaceBlur: 32,
    surfaceSaturation: 1.6,
  ),
  theme: appTheme,
  home: const HomePage(),
)
// Web — 시트가 모든 스타일의 블록을 한 벌에 굽기 때문에 맵
ThemeConfig(
  palette,
  surfaceTunings: const {
    CoreSurfaceStyleId.liquidGlass: CoreSurfaceTuning(
      surfaceOpacity: 0.5,
      surfaceBlur: 32,
      surfaceSaturation: 1.6,
    ),
  },
)

단수와 복수의 차이는 두 transport 의 성질입니다 — Flutter 는 활성 테마 하나를 만들고, Web 은 스타일시트 한 벌에 모든 스타일의 블록을 함께 굽습니다.

필드무엇
surfaceOpacity표면 채움의 알파
surfaceBlur배경 블러 반경
surfaceSaturation배경 채도 배율
sheenWidth엣지 띠 두께
sheenFade코너에서 풀 강도까지의 변 길이 비율

범위를 벗어난 값은 컴파일 에러입니다. 조용히 잘라내면 앱이 적은 값과 화면 값이 달라지고 아무 신호도 남지 않기 때문입니다. 예를 들어 surfaceOpacity: 1.0 은 빌드되지 않습니다 — 불투명하면 배경이 없어 블러와 채도가 아무 일도 하지 않는데, 프리셋은 계속 반투명이라고 선언하게 됩니다.

정할 수 없는 것#

띠의 , 표면의 종류, 그리고 접근성 계약은 프리셋의 진술이라 열려 있지 않습니다.

띠 색이 특히 그렇습니다 — 색이 토큰으로 남아 있어야 고대비 모드가 그것을 같이 옮깁니다. 임의 색을 받으면 다른 모든 것이 고대비로 이동할 때 띠만 제자리에 남습니다.

플랫폼 비대칭#

FlutterWeb
반투명 · 블러 · 채도 · 엣지 시인
축을 정하는 손잡이앱 루트 생성자 필드SurfaceStyleScope.setSurfaceStyle
축을 읽는 손잡이 SurfaceStyleScope.of SurfaceStyleScope.of

재질 자체는 양 플랫폼이 같은 값을 같은 곳에서 읽습니다.

굴절은 아직 출하되지 않았습니다#

레퍼런스 구현들이 유리에 얹는 굴절(가장자리에서 배경이 휘어 보이는 것)은 이 킷에 없습니다.

기술적으로 불가능해서가 아닙니다 — 실기기에서 재 본 결과 Flutter 는 Impeller 에서, Web 은 Chromium 에서 실제로 배경을 옮깁니다. 다만 굴절은 요소 크기마다 변위 맵을 다시 구워야 해서 이 축의 transport(스타일시트 한 벌 + CSS 변수)로 표현되지 않고, 그래서 축이 아니라 표면이 여는 별도 역량으로 들어갈 예정입니다.

두 자리는 지금도 확정입니다. Flutter Web 에서는 불가능합니다 — 그 SDK 가 필요한 API 를 무조건 throw 로 정의합니다. Safari 와 Firefox 도 렌더하지 않습니다. 두 브라우저는 값을 파싱해서 "지원한다" 고 답하지만 그리지는 않으므로, 기능 감지로는 구분되지 않습니다.

함께 보기#

  • 테마 — 색·타이포그래피 등 축 바깥의 테마 시스템
  • 디자인 토큰 — 색·타입·간격·형태·효과·모션 맵
  • 색상 — 시맨틱 색 토큰과 팔레트