スタイルに関する推奨事項と禁止事項

このページでは、コードベース全体で一貫性を実現するスタイルの使用に関する効果的な手法と、API の設計時に従った原則について説明します。

推奨事項

次のベスト プラクティスに従ってください。

推奨事項: ビジュアルにはスタイルを使用し、動作には修飾子を使用する

ビジュアル構成(背景、パディング、ボーダー)には Styles API を使用し、クリック ロジック、ジェスチャー検出、ユーザー補助などの動作には修飾子を使用します。

推奨事項: デザイン システムでスタイル パラメータを公開する

独自のカスタム デザイン システム コンポーネントの場合は、修飾子パラメータの後に Style オブジェクトを公開する必要があります。

@Composable
fun GradientButton(
    modifier: Modifier = Modifier,
    // ✅ DO: for design system components, expose a style modifier to consumers to be able to customize the components
    style: Style = Style
) {
    // Consume the style
}

推奨事項: ビジュアルベースのパラメータをスタイルに置き換える

コンポーザブルのパラメータを 1 つの Style パラメータに置き換えることを検討してください。 例:

// Before
@Composable
fun OldButton(background: Color, fontColor: Color) {
}

// After
// ✅ DO: Replace visual-based parameters with a style that includes same properties
@Composable
fun NewButton(style: Style = Style) {
}

推奨事項: アニメーションにはスタイルを優先する

組み込みの animate ブロックを使用して、状態ベースのスタイリングにアニメーションを使用すると、修飾子よりもパフォーマンスが向上します。

推奨事項: 「後書き優先」を利用する

style プロパティはスタックするのではなく上書きされるという特性を利用します。これを使用すると、複数のパラメータを使用せずに、デフォルトのコンポーネントのボーダーや背景をオーバーライドできます。

禁止事項

次のパターンは推奨されません。

禁止事項: 操作ロジックにスタイルを使用しない

スタイル内で onClick やジェスチャー検出を処理しないでください。スタイルは状態に基づくビジュアル構成に限定されるため、ビジネス ロジックを処理すべきではありません。状態に基づいてビジュアルを変更するだけにする必要があります。

禁止事項: デフォルトのスタイルをデフォルト パラメータとして指定しない

スタイル パラメータは常に style: Style = Style を使用して宣言する必要があります。

@Composable
fun BadButton(
    modifier: Modifier = Modifier,
    // ❌ DON'T set a default style here as a parameter
    style: Style = Style { background(Color.Red) }
) {
}

「デフォルト」パラメータを含めるには、受信したパラメータ スタイルを定義済みのデフォルトとマージします。

@Composable
fun GoodButton(
    modifier: Modifier = Modifier,
    // ✅ Do: always pass it as a Style, do not pass other defaults
    style: Style = Style
) {
    // ...
    val defaultStyle = Style { background(Color.Red) }
    // ✅ Do Combine defaults inside with incoming parameter
    Box(modifier = modifier.styleable(styleState, defaultStyle, style)) {
      // your logic
    }
}

禁止事項: レイアウトベースのコンポーザブルにスタイル パラメータを指定しない

任意のコンポーザブルにスタイルを指定できますが、レイアウトベースのコンポーザブルや画面レベルのコンポーザブルがスタイルを受け入れることは想定されていません。コンシューマーの観点からすると、このレベルでスタイルが何をするのかが不明確です。 スタイルはコンポーネント向けに設計されており、必ずしもレイアウト向けではありません。

禁止事項: Composition でスタイルを作成しない

CompositionLocals は、使用される場所ではなく、スタイルが定義された時点で読み取られます。スタイルが実際に使用されるときに、CompositionLocal の状態が変更されている可能性があり、不正確なスタイルになることがあります。

// DON'T - Create styles in Composition that access composition locals in this way - this will likely lead to issues when style is used / accessed, as it would not get updated when the value changes.
@Composable
fun containerStyle(): Style {
    val background = MaterialTheme.colorScheme.background
    val onBackground = MaterialTheme.colorScheme.onBackground
    return Style {
        background(background)
        contentColor(onBackground)
    }
}

// Do: Instead, Create StyleScope extension functions for your subsystems to access themed composition Locals
val StyleScope.colors: JetsnackColors
    get() = JetsnackTheme.LocalJetsnackTheme.currentValue.colors

val StyleScope.typography: androidx.compose.material3.Typography
    get() = JetsnackTheme.LocalJetsnackTheme.currentValue.typography
val StyleScope.shapes: Shapes
    get() = JetsnackTheme.LocalJetsnackTheme.currentValue.shapes
// Access CompositionLocals
val button = Style {
    background(colors.brandSecondary)
    shape(shapes.small)
}

推奨事項: サブシステムの値の変更に対して 1 つのスタイルを作成する

たとえば、ダークモードとライトモードを切り替える場合は、既存のテーマの値(CompositionLocal を介して)をクエリして、Style を動的に変更します。

// Do: Use CompositionLocals or themed values to create a single style
val buttonStyle = Style {
    background(colors.brandSecondary)
    shape(shapes.small)
}

推奨事項: テーマ定義間でコンポーネントが根本的に異なる場合は、スタイル全体を切り替える

テーマが根本的に異なる場合は、テーマレベルでスタイル オブジェクト全体を切り替えることができます。

たとえば、商品 / ページまたはオファーごとに異なるテーマがあり、スタイルのプロパティが多数異なるアプリを作成する場合は、テーマレベルでスタイル セット全体を切り替えることができます。

// DO Switch out whole styles when many properties differ - if Product A and Product B are two white labelled apps that provide different Themes.
val productBThemedButton = Style {
    shape(shapes.small)
    background(colors.brandSecondary)
    // other properties are fundamentally different
}

val productAThemedButton = Style {
    shape(shapes.large)
    background(colors.brand)
    // other properties are fundamentally different
}