Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Custom Design Systems in Compose UI

Custom Design Systems in Compose UI

While Jetpack Compose comes with Google's Material Design system, many apps have their own design language and systems that they need to adhere to in order to offer consistency across platforms. As apps and teams scale, it eventually becomes beneficial to implement your own design system.

That's exactly the case at Dropbox. We recently went through the process of implementing our DIG design library in Compose, and have seen the benefits first hand.

In this talk, we'll discuss different approaches to implementing custom design systems in Compose, from customizing the Material Theme, to extending it, all the way to implementing a full blown custom design library. We'll cover some of the benefits and drawbacks of each approach that you might want to consider.

Lastly, we'll share the process we went through at Dropbox as we implemented our DIG design library. We'll report on our experience and reflect on the benefits we've seen and what our plans are for the future.

Ryan Harter

June 03, 2022
Tweet

More Decks by Ryan Harter

Other Decks in Programming

Transcript

  1. Ryan Harter @rharter
    Custom Design Systems
    in Jetpack Compose

    View Slide

  2. Ryan Harter @RHARter
    Custom Design Systems
    in Jetpack Compose UI

    View Slide

  3. Ryan Harter @RHARter
    Follow Along
    https://ryans.link/dsys-compose

    View Slide

  4. Ryan Harter @RHARter
    See Also
    Compose beyond Material
    Sebastiano Poggi

    View Slide

  5. Compose UI

    View Slide

  6. Compose UI
    Compose UI

    View Slide

  7. Compose UI
    Compose UI
    Foundation

    View Slide

  8. Compose UI
    Compose UI
    Foundation
    Compose Material

    View Slide

  9. Compose Material
    Material Theme
    Colors Typography
    Button Text Surface Etc.
    Shape

    View Slide

  10. Compose Material
    Material Theme
    Colors Typography
    Button Text Surface Etc.
    Shape

    View Slide

  11. Sidebar
    Design System Tokens

    View Slide

  12. Sidebar
    Design System Tokens
    @Stable


    class Colors(


    val primary: Color,


    val primaryVariant: Color,


    val secondary: Color,


    val secondaryVariant: Color,


    val background: Color,


    val surface: Color,


    val error: Color,


    val onPrimary: Color,


    val onSecondary: Color,


    val onBackground: Color,


    val onSurface: Color,


    val onError: Color,


    val isLight: Boolean


    )

    View Slide

  13. Sidebar
    Design System Tokens
    @Stable


    class Colors(


    val primary: Color,


    val primaryVariant: Color,


    val secondary: Color,


    val secondaryVariant: Color,


    val background: Color,


    val surface: Color,


    val error: Color,


    val onPrimary: Color,


    val onSecondary: Color,


    val onBackground: Color,


    val onSurface: Color,


    val onError: Color,


    val isLight: Boolean


    )
    @Stable


    class Colors(


    val : Color,


    val : Color,


    val : Color,


    val : Color,


    val : Color,


    val : Color,


    val : Color,


    val : Color,


    val : Color,


    val : Color,


    val : Color,


    val : Color,


    val : Boolean


    )

    View Slide

  14. Sidebar
    Design System Tokens
    @Stable


    class Colors(


    val primary: Color,


    val primaryVariant: Color,


    val secondary: Color,


    val secondaryVariant: Color,


    val background: Color,


    val surface: Color,


    val error: Color,


    val onPrimary: Color,


    val onSecondary: Color,


    val onBackground: Color,


    val onSurface: Color,


    val onError: Color,


    val isLight: Boolean


    )
    @Immutable


    class Typography(


    val h1: TextStyle,


    val h2: TextStyle,


    val h3: TextStyle,


    val h4: TextStyle,


    val h5: TextStyle,


    val h6: TextStyle,


    val subtitle1: TextStyle,


    val subtitle2: TextStyle,


    val body1: TextStyle,


    val body2: TextStyle,


    val button: TextStyle,


    val caption: TextStyle,


    val overline: TextStyle


    )

    View Slide

  15. Sidebar
    Design System Tokens
    @Stable


    class Colors(


    val primary: Color,


    val primaryVariant: Color,


    val secondary: Color,


    val secondaryVariant: Color,


    val background: Color,


    val surface: Color,


    val error: Color,


    val onPrimary: Color,


    val onSecondary: Color,


    val onBackground: Color,


    val onSurface: Color,


    val onError: Color,


    val isLight: Boolean


    )
    @Immutable


    class Typography(


    val h1: TextStyle,


    val h2: TextStyle,


    val h3: TextStyle,


    val h4: TextStyle,


    val h5: TextStyle,


    val h6: TextStyle,


    val subtitle1: TextStyle,


    val subtitle2: TextStyle,


    val body1: TextStyle,


    val body2: TextStyle,


    val button: TextStyle,


    val caption: TextStyle,


    val overline: TextStyle


    )
    @Immutable


    class Typography(


    val : TextStyle,


    val : TextStyle,


    val : TextStyle,


    val : TextStyle,


    val : TextStyle,


    val : TextStyle,


    val : TextStyle,


    val : TextStyle,


    val : TextStyle,


    val : TextStyle,


    val : TextStyle,


    val : TextStyle,


    val : TextStyle


    )

    View Slide

  16. Sidebar
    Design System Tokens
    @Stable


    class Colors(


    val primary: Color,


    val primaryVariant: Color,


    val secondary: Color,


    val secondaryVariant: Color,


    val background: Color,


    val surface: Color,


    val error: Color,


    val onPrimary: Color,


    val onSecondary: Color,


    val onBackground: Color,


    val onSurface: Color,


    val onError: Color,


    val isLight: Boolean


    )
    @Immutable


    class Typography(


    val h1: TextStyle,


    val h2: TextStyle,


    val h3: TextStyle,


    val h4: TextStyle,


    val h5: TextStyle,


    val h6: TextStyle,


    val subtitle1: TextStyle,


    val subtitle2: TextStyle,


    val body1: TextStyle,


    val body2: TextStyle,


    val button: TextStyle,


    val caption: TextStyle,


    val overline: TextStyle


    )

    View Slide

  17. Compose Material
    Material Theme
    Colors Typography
    Button Text Surface Etc.
    Shape

    View Slide

  18. Compose Material
    Material Theme
    Button Text Surface Etc.
    Colors Typography Shape

    View Slide

  19. Material Theme

    View Slide

  20. Customizing
    • Customize Material

    • Extend Material

    • Implement fully-custom design system
    Material Theme

    View Slide

  21. Material Theme
    Button Text Surface Etc.
    Colors Typography Shape
    Customize Material

    View Slide

  22. Material Theme
    Colors Typography
    Button Text Surface Etc.
    Shape
    Customize Material

    View Slide

  23. private val LightColors = lightColors(
    .
    ..
    )


    private val DarkColors = darkColors(
    .
    ..
    )




    @Composable


    fun MyTheme(content: @Composable ()
    -
    >
    Unit) {


    MaterialTheme(


    colors = if (isSystemInDarkTheme()) DarkColors else LightColors,


    shapes = Shapes(


    small = RoundedCornerShape(0.dp),


    medium = RoundedCornerShape(0.dp),


    large = RoundedCornerShape(0.dp)


    ),


    content = content


    )


    }
    Customize Material

    View Slide

  24. private val LightColors = lightColors(
    .
    ..
    )


    private val DarkColors = darkColors(
    .
    ..
    )




    @Composable


    fun MyTheme


    MaterialTheme(


    colors = if (isSystemInDarkTheme()) DarkColors else LightColors,


    shapes = Shapes(


    small = RoundedCornerShape(0.dp),


    medium = RoundedCornerShape(0.dp),


    large = RoundedCornerShape(0.dp)


    ),


    content = content


    )


    }
    Customize Material
    (content: @Composable ()
    - >
    Unit) {

    View Slide

  25. private val LightColors = lightColors(
    .
    ..
    )


    private val DarkColors = darkColors(
    .
    ..
    )




    @Composable


    fun MyTheme


    MaterialTheme(


    colors = if (isSystemInDarkTheme()) DarkColors else LightColors,


    shapes = Shapes(


    small = RoundedCornerShape(0.dp),


    medium = RoundedCornerShape(0.dp),


    large = RoundedCornerShape(0.dp)


    ),


    content = content


    )


    }
    : @Composable ()
    ->
    Unit) {
    Customize Material
    (content

    View Slide

  26. private val LightColors = lightColors(
    .
    ..
    )


    private val DarkColors = darkColors(
    .
    ..
    )




    @Composable


    fun MyTheme


    MaterialTheme(


    colors = if (isSystemInDarkTheme()) DarkColors else LightColors,


    shapes = Shapes(


    small = RoundedCornerShape(0.dp),


    medium = RoundedCornerShape(0.dp),


    large = RoundedCornerShape(0.dp)


    ),


    content = content


    )


    }
    : @Composable ()
    ->
    Unit) {
    (content
    Customize Material
    content

    View Slide

  27. private val LightColors = lightColors(
    .
    ..
    )


    private val DarkColors = darkColors(
    .
    ..
    )




    @Composable


    fun MyTheme


    MaterialTheme(


    colors = if (isSystemInDarkTheme()) DarkColors else LightColors,


    shapes = Shapes(


    small = RoundedCornerShape(0.dp),


    medium = RoundedCornerShape(0.dp),


    large = RoundedCornerShape(0.dp)


    ),


    content = content


    )


    }
    private val LightColors = lightColors(
    .
    ..
    )


    private val DarkColors = darkColors(
    .
    ..
    )




    @Composable


    fun MyTheme(content: @Composable ()
    -
    >
    Unit) {


    MaterialTheme(


    colors = if (isSystemInDarkTheme()) DarkColors else LightColors,


    shapes = Shapes(


    small = RoundedCornerShape(0.dp),


    medium = RoundedCornerShape(0.dp),


    large = RoundedCornerShape(0.dp)


    ),


    content = content


    )


    }
    Customize Material
    : @Composable ()
    ->
    Unit) {
    content
    (content

    View Slide

  28. private val LightColors = lightColors(
    .
    ..
    )


    private val DarkColors = darkColors(
    .
    ..
    )




    @Composable


    fun MyTheme


    MaterialTheme(


    colors = if (isSystemInDarkTheme()) DarkColors else LightColors,


    shapes = Shapes(


    small = RoundedCornerShape(0.dp),


    medium = RoundedCornerShape(0.dp),


    large = RoundedCornerShape(0.dp)


    ),


    content = content


    )


    }
    private val LightColors = lightColors(
    .
    ..
    )


    private val DarkColors = darkColors(
    .
    ..
    )




    @Composable


    fun MyTheme(content: @Composable ()
    -
    >
    Unit) {


    MaterialTheme(


    colors = if (isSystemInDarkTheme()) DarkColors else LightColors,


    shapes = Shapes(


    small = RoundedCornerShape(0.dp),


    medium = RoundedCornerShape(0.dp),


    large = RoundedCornerShape(0.dp)


    ),


    content = content


    )


    }
    Customize Material
    : @Composable ()
    >

    View Slide

  29. private val LightColors = lightColors(
    .
    ..
    )


    private val DarkColors = darkColors(
    .
    ..
    )


    @Composable


    fun MyTheme(content: @Composable ()
    -
    >
    Unit) {


    MaterialTheme(


    colors = if (isSystemInDarkTheme()) DarkColors else LightColors,


    shapes = Shapes(


    small = RoundedCornerShape(0.dp),


    medium = RoundedCornerShape(0.dp),


    large = RoundedCornerShape(0.dp)


    ),


    content = content


    )


    }
    Customize Material

    View Slide

  30. private val LightColors = lightColors(
    .
    ..
    )


    private val DarkColors = darkColors(
    .
    ..
    )


    @Composable


    fun MyTheme(content: @Composable ()
    -
    >
    Unit) {


    MaterialTheme(


    colors = if (isSystemInDarkTheme()) DarkColors else LightColors,


    shapes = Shapes(


    small = RoundedCornerShape(0.dp),


    medium = RoundedCornerShape(0.dp),


    large = RoundedCornerShape(0.dp)


    ),


    content = content


    )


    }
    Customize Material

    View Slide

  31. private val LightColors = lightColors(
    .
    ..
    )


    private val DarkColors = darkColors(
    .
    ..
    )




    @Composable


    fun MyTheme(content: @Composable ()
    -
    >
    Unit) {


    MaterialTheme(


    colors = if (isSystemInDarkTheme()) DarkColors else LightColors,


    shapes = Shapes(


    small = RoundedCornerShape(0.dp),


    medium = RoundedCornerShape(0.dp),


    large = RoundedCornerShape(0.dp)


    ),


    content = content


    )


    }
    Customize Material

    View Slide

  32. private val LightColors = lightColors(
    .
    ..
    )


    private val DarkColors = darkColors(
    .
    ..
    )




    @Composable


    fun MyTheme(content: @Composable ()
    -
    >
    Unit) {


    MaterialTheme(


    colors = if (isSystemInDarkTheme()) DarkColors else LightColors,


    shapes = Shapes(


    small = RoundedCornerShape(0.dp),


    medium = RoundedCornerShape(0.dp),


    large = RoundedCornerShape(0.dp)


    ),


    content = content


    )


    }
    Customize Material
    @Composable


    View Slide



  33. fun MyApp() {


    MyTheme {


    . ..

    }


    }

    View Slide

  34. Customizing
    • Customize Material

    • Extend Material

    • Implement fully-custom design system
    Material Theme

    View Slide

  35. Extend Material

    View Slide

  36. Extend Material (super simple)
    val Colors.snackbarAction: Color


    get() = if (isLight) Red300 else Red700


    val Typography.textFieldInput: TextStyle


    get() = TextStyle(
    / * ... */
    )


    val Shapes.card: Shape


    get() = RoundedCornerShape(size = 20.dp)


    val Colors.snackbarAction: Color


    get() = if (isLight) Red300 else Red700


    val Typography.textFieldInput: TextStyle


    get() = TextStyle(
    / * ... */
    )


    val Shapes.card: Shape


    get() = RoundedCornerShape(size = 20.dp)


    View Slide

  37. Extend Material

    View Slide

  38. Sidebar
    Providing Tokens
    Material Theme
    Button Text Surface Etc.
    Colors Typography Shape

    View Slide

  39. Sidebar
    Surface 1
    Material Theme
    Providing Tokens
    Button Text Surface Etc.
    Colors Typography Shape

    View Slide

  40. Sidebar
    Material Theme
    Colors Typography Shape
    Surface 1
    Providing Tokens
    Button
    Text
    Surface

    View Slide

  41. Sidebar
    Material Theme
    Colors Typography Shape
    Surface 1
    Providing Tokens
    Button
    Text
    Surface
    Surface 2
    Surface
    Material Theme 2
    Text Text Button
    Material Theme
    Colors Typography Shape

    View Slide

  42. Sidebar
    Material Theme
    Colors Typography Shape
    Surface 1
    Providing Tokens
    Button
    Text
    Surface
    Surface 2
    Surface
    Material Theme 2
    Text Text Button
    Material Theme
    Colors Typography Shape

    View Slide

  43. Sidebar
    Material Theme
    Colors Typography Shape
    Surface 1
    Providing Tokens
    Button
    Text
    Surface
    Surface 2
    Surface
    Material Theme 2
    Text Text
    Material Theme
    Colors Typography Shape
    Button

    View Slide

  44. Sidebar
    Material Theme
    Colors Typography Shape
    Surface 1
    Providing Tokens
    Button
    Text
    Surface
    Surface 2
    Surface
    Material Theme 2
    Text Text
    Material Theme
    Colors Typography Shape
    Button

    View Slide

  45. Sidebar
    Material Theme
    Colors Typography Shape
    Surface 1
    Providing Tokens
    Button
    Text
    Surface
    Surface 2
    Surface
    Material Theme 2
    Text Text
    Material Theme
    Colors Typography Shape
    Button

    View Slide

  46. Extend Material

    View Slide

  47. @Immutable


    data class ExtendedColors(


    val tertiary: Color,


    val onTertiary: Color


    )
    val LocalExtendedColors = staticCompositionLocalOf {


    View Slide

  48. @Immutable


    ExtendedColors(


    tertiary = Color.Unspecified,


    onTertiary = Color.Unspecified


    )


    }
    @Composable


    View Slide



  49. fun ExtendedTheme(


    /* .. . * /

    content: @Composable ()
    -
    >
    Unit


    ) {


    val extendedColors = ExtendedColors(


    tertiary = Color(0xFFA8EFF0),


    onTertiary = Color(0xFF002021)


    )


    CompositionLocalProvider(LocalExtendedColors provides extendedColors) {


    MaterialTheme(


    /*
    colors =
    ...
    , typography =
    ..
    .
    , shapes =
    ..
    . */

    content = content


    )


    }


    }
    Extend Material

    View Slide



  50. fun ExtendedTheme(


    /* .. . * /

    content: @Composable ()
    -
    >
    Unit


    ) {


    val extendedColors = ExtendedColors(


    tertiary = Color(0xFFA8EFF0),


    onTertiary = Color(0xFF002021)


    )


    CompositionLocalProvider(LocalExtendedColors provides extendedColors) {


    MaterialTheme(


    /*
    colors =
    ...
    , typography =
    ..
    .
    , shapes =
    ..
    . */

    content = content


    )


    }


    }
    Extend Material

    View Slide



  51. fun ExtendedTheme(


    /* .. . * /

    content: @Composable ()
    -
    >
    Unit


    ) {


    val extendedColors = ExtendedColors(


    tertiary = Color(0xFFA8EFF0),


    onTertiary = Color(0xFF002021)


    )


    CompositionLocalProvider(LocalExtendedColors provides extendedColors) {


    MaterialTheme(


    /*
    colors =
    ...
    , typography =
    ..
    .
    , shapes =
    ..
    . */

    content = content


    )


    }


    }
    Extend Material

    View Slide



  52. fun ExtendedTheme(


    /* .. . * /

    content: @Composable ()
    -
    >
    Unit


    ) {


    val extendedColors = ExtendedColors(


    tertiary = Color(0xFFA8EFF0),


    onTertiary = Color(0xFF002021)


    )


    CompositionLocalProvider(LocalExtendedColors provides extendedColors) {


    MaterialTheme(


    /*
    colors =
    ...
    , typography =
    ..
    .
    , shapes =
    ..
    . */

    content = content


    )


    }


    }
    Extend Material

    View Slide



  53. fun ExtendedTheme(


    /* .. . * /

    content: @Composable ()
    -
    >
    Unit


    ) {


    val extendedColors = ExtendedColors(


    tertiary = Color(0xFFA8EFF0),


    onTertiary = Color(0xFF002021)


    )


    CompositionLocalProvider(LocalExtendedColors provides extendedColors) {


    MaterialTheme(


    /*
    colors =
    ...
    , typography =
    ..
    .
    , shapes =
    ..
    . */

    content = content


    )


    }


    }
    Extend Material

    View Slide



  54. fun ExtendedTheme(


    /* .. . * /

    content: @Composable ()
    -
    >
    Unit


    ) {


    val extendedColors = ExtendedColors(


    tertiary = Color(0xFFA8EFF0),


    onTertiary = Color(0xFF002021)


    )


    CompositionLocalProvider(LocalExtendedColors provides extendedColors) {


    MaterialTheme(


    /*
    colors =
    ...
    , typography =
    ..
    .
    , shapes =
    ..
    . */

    content = content


    )


    }


    }
    Extend Material

    View Slide



  55. fun ExtendedTheme(


    /* .. . * /

    content: @Composable ()
    -
    >
    Unit


    ) {


    val extendedColors = ExtendedColors(


    tertiary = Color(0xFFA8EFF0),


    onTertiary = Color(0xFF002021)


    )


    CompositionLocalProvider(LocalExtendedColors provides extendedColors) {


    MaterialTheme(


    /*
    colors =
    ...
    , typography =
    ..
    .
    , shapes =
    ..
    . */

    content = content


    )


    }


    }
    Extend Material

    View Slide

  56. //
    Use with eg. ExtendedTheme.colors.tertiary


    object ExtendedTheme {


    val colors: ExtendedColors


    @Composable


    get() = LocalExtendedColors.current


    }
    tertiary = Color(0xFFA8EFF0),


    View Slide

  57. object ExtendedTheme {


    fun ExtendedButton(


    onClick: ()
    ->
    Unit,


    modifier: Modifier = Modifier,


    content: @Composable RowScope.()
    ->
    Unit


    ) {


    Button(


    colors = ButtonDefaults.buttonColors(


    backgroundColor = ExtendedTheme.colors.tertiary,


    contentColor = ExtendedTheme.colors.onTertiary


    /*
    Other colors use values from MaterialTheme
    */

    ),


    onClick = onClick,


    modifier = modifier,


    content = content


    )


    }


    View Slide

  58. object ExtendedTheme {


    fun ExtendedButton(


    onClick: ()
    ->
    Unit,


    modifier: Modifier = Modifier,


    content: @Composable RowScope.()
    ->
    Unit


    ) {


    Button(


    colors = ButtonDefaults.buttonColors(


    backgroundColor = ExtendedTheme.colors.tertiary,


    contentColor = ExtendedTheme.colors.onTertiary


    /*
    Other colors use values from MaterialTheme
    */

    ),


    onClick = onClick,


    modifier = modifier,


    content = content


    )


    }


    View Slide

  59. object ExtendedTheme {


    fun ExtendedButton(


    onClick: ()
    ->
    Unit,


    modifier: Modifier = Modifier,


    content: @Composable RowScope.()
    ->
    Unit


    ) {


    Button(


    colors = ButtonDefaults.buttonColors(


    backgroundColor = ExtendedTheme.colors.tertiary,


    contentColor = ExtendedTheme.colors.onTertiary


    /*
    Other colors use values from MaterialTheme
    */

    ),


    onClick = onClick,


    modifier = modifier,


    content = content


    )


    }


    View Slide

  60. object ExtendedTheme {


    fun ExtendedButton(


    onClick: ()
    ->
    Unit,


    modifier: Modifier = Modifier,


    content: @Composable RowScope.()
    ->
    Unit


    ) {


    Button(


    colors = ButtonDefaults.buttonColors(


    backgroundColor = ExtendedTheme.colors.tertiary,


    contentColor = ExtendedTheme.colors.onTertiary


    /*
    Other colors use values from MaterialTheme
    */

    ),


    onClick = onClick,


    modifier = modifier,


    content = content


    )


    }


    View Slide

  61. object ExtendedTheme {


    fun ExtendedButton(


    onClick: ()
    ->
    Unit,


    modifier: Modifier = Modifier,


    content: @Composable RowScope.()
    ->
    Unit


    ) {


    Button(


    colors = ButtonDefaults.buttonColors(


    backgroundColor = ExtendedTheme.colors.tertiary,


    contentColor = ExtendedTheme.colors.onTertiary


    /*
    Other colors use values from MaterialTheme
    */

    ),


    onClick = onClick,


    modifier = modifier,


    content = content


    )


    }


    View Slide

  62. Customizing
    • Customize Material

    • Extend Material

    • Implement fully-custom design system
    Material Theme

    View Slide

  63. Fully Custom

    View Slide

  64. Fully Custom
    @Composable


    fun CustomTheme(


    /* .. . * /

    content: @Composable ()
    -
    >
    Unit


    ) {


    val customColors =
    ...

    val customTypography =
    ..
    .

    val customElevation = CustomElevation(


    default = 4.dp,


    pressed = 8.dp


    )


    CompositionLocalProvider(


    LocalCustomColors provides customColors,


    LocalCustomTypography provides customTypography,


    LocalCustomElevation provides customElevation,


    content = content


    )


    }


    View Slide

  65. Fully Custom
    @Composable


    fun CustomTheme(


    /* .. . * /

    content: @Composable ()
    -
    >
    Unit


    ) {


    val customColors =
    ...

    val customTypography =
    ..
    .

    val customElevation = CustomElevation(


    default = 4.dp,


    pressed = 8.dp


    )


    CompositionLocalProvider(


    LocalCustomColors provides customColors,


    LocalCustomTypography provides customTypography,


    LocalCustomElevation provides customElevation,


    content = content


    )


    }


    View Slide

  66. Fully Custom
    @Composable


    fun CustomTheme(


    /* .. . * /

    content: @Composable ()
    -
    >
    Unit


    ) {


    val customColors =
    ...

    val customTypography =
    ..
    .

    val customElevation = CustomElevation(


    default = 4.dp,


    pressed = 8.dp


    )


    CompositionLocalProvider(


    LocalCustomColors provides customColors,


    LocalCustomTypography provides customTypography,


    LocalCustomElevation provides customElevation,


    content = content


    )


    }


    View Slide

  67. @Composable


    fun CustomTheme(


    /* .. . * /

    content: @Composable ()
    -
    >
    Unit


    ) {


    val customColors =
    ...

    val customTypography =
    ..
    .

    val customElevation = CustomElevation(


    default = 4.dp,


    pressed = 8.dp


    )


    CompositionLocalProvider(


    LocalCustomColors provides customColors,


    LocalCustomTypography provides customTypography,


    LocalCustomElevation provides customElevation,


    content = content


    )


    }


    Fully Custom

    View Slide

  68. @Composable


    fun CustomTheme(


    /* .. . * /

    content: @Composable ()
    -
    >
    Unit


    ) {


    val customColors =
    ...

    val customTypography =
    ..
    .

    val customElevation = CustomElevation(


    default = 4.dp,


    pressed = 8.dp


    )


    CompositionLocalProvider(


    LocalCustomColors provides customColors,


    LocalCustomTypography provides customTypography,


    LocalCustomElevation provides customElevation,


    content = content


    )


    }


    Fully Custom

    View Slide

  69. @Composable


    fun CustomTheme(


    /* .. . * /

    content: @Composable ()
    -
    >
    Unit


    ) {


    val customColors =
    ...

    val customTypography =
    ..
    .

    val customElevation = CustomElevation(


    default = 4.dp,


    pressed = 8.dp


    )


    CompositionLocalProvider(


    LocalCustomColors provides customColors,


    LocalCustomTypography provides customTypography,


    LocalCustomElevation provides customElevation,


    content = content


    )


    }


    Fully Custom

    View Slide

  70. @Composable


    fun CustomButton(


    onClick: ()
    ->
    Unit,


    modifier: Modifier = Modifier,


    content: @Composable RowScope.()
    ->
    Unit


    ) {


    Button(


    colors = ButtonDefaults.buttonColors(


    backgroundColor = CustomTheme.colors.component,


    contentColor = CustomTheme.colors.content,


    disabledBackgroundColor = CustomTheme.colors.content


    .copy(alpha = 0.12f)


    .compositeOver(CustomTheme.colors.component),


    disabledContentColor = CustomTheme.colors.content


    .copy(alpha = ContentAlpha.disabled)


    ),


    shape = ButtonShape,



    LocalCustomColors provides customColors,


    View Slide



  71. .copy(alpha = 0.12f)


    .compositeOver(CustomTheme.colors.component),


    disabledContentColor = CustomTheme.colors.content


    .copy(alpha = ContentAlpha.disabled)


    ),


    shape = ButtonShape,


    elevation = ButtonDefaults.elevation(


    defaultElevation = CustomTheme.elevation.default,


    pressedElevation = CustomTheme.elevation.pressed


    ),


    onClick = onClick,


    modifier = modifier,


    content = {


    ProvideTextStyle(


    value = CustomTheme.typography.body


    ) {


    content()


    }


    }


    )


    }


    Fully Custom

    View Slide

  72. @Dropbox

    View Slide

  73. Where we started
    • Already have design system

    • Color Tokens

    • Typography Tokens

    • Components

    View Slide

  74. Goals
    • Get started fast

    • Easy to use (no cheat sheet)

    • Access to all of the existing Material composables

    • Use Compose while implementing design system

    • Incrementally migrate away from Material

    View Slide

  75. • Customize Material

    • Extend Material
    • Implement fully-custom design system
    Customizing Material Theme

    View Slide

  76. • Customize Material

    • Extend Material
    • Wrap Material
    • Implement fully-custom design system
    Customizing Material Theme

    View Slide

  77. • Customize Material

    • Extend Material
    • Wrap Material
    • Implement fully-custom design system
    Customizing Material Theme

    View Slide

  78. Wrap Material

    View Slide

  79. @Stable


    class Colors(


    primary: Color,


    secondary: Color,


    accent: Color,


    buttonPrimaryHover: Color,


    buttonPrimaryActive: Color,





    )


    View Slide

  80. /**

    * Converts the Dig colors to Material colors, so that standard Material


    * composables use Dig colors out of the box.


    */

    internal fun Colors.toMaterialColors(): MaterialColors = MaterialColors(


    primary = accent,


    primaryVariant = accent,


    secondary = accent,


    secondaryVariant = accent,


    background = standard.background,


    surface = standard.background,


    error = alert.text,


    onPrimary = if (isLight) secondary else standard.text,


    onSecondary = if (isLight) standard.text else secondary,


    onBackground = standard.text,


    onSurface = standard.text,


    onError = if (isLight) secondary else standard.text,


    isLight = isLight,


    )


    Wrap Material

    View Slide

  81. @Immutable


    data class Typography internal constructor(


    val titleLarge: TextStyle,


    val titleStandard: TextStyle,


    val titleSmall: TextStyle,


    val labelXLarge: TextStyle,





    )
    internal fun Typography.toMaterialTypography(): MaterialTypography = MaterialTypography(


    View Slide

  82. val titleLarge: TextStyle,


    defaultFontFamily = DefaultFontFamily,


    h1 = titleLarge,


    h2 = titleLarge,


    h3 = titleLarge,


    h4 = titleLarge,


    h5 = titleStandard,


    h6 = titleStandard.copy(fontWeight = FontWeight.Medium),


    subtitle1 = paragraphLarge,


    subtitle2 = paragraphStandard,


    body1 = paragraphLarge,


    body2 = paragraphStandard,


    button = labelLarge.copy(fontWeight = FontWeight.Medium, lineHeight = 24.sp),


    caption = labelSmall,


    overline = labelXSmall,


    )
    Wrap Material

    View Slide

  83. Wrap Material
    @Composable


    fun DigTheme(


    typography: Typography = DigTheme.typography,


    colors: Colors = DigTheme.colors,


    content: @Composable ()
    -
    >
    Unit


    ) {


    val rememberedColors = …


    CompositionLocalProvider(


    LocalColors provides rememberedColors,


    LocalTypography provides typography


    ) {


    MaterialTheme(


    colors = rememberedColors.toMaterialColors(),


    typography = typography.toMaterialTypography(),


    shapes = …,


    content = content


    )


    }


    }

    View Slide

  84. Wrap Material
    @Composable


    fun DigTheme(


    typography: Typography = DigTheme.typography,


    colors: Colors = DigTheme.colors,


    content: @Composable ()
    -
    >
    Unit


    ) {


    val rememberedColors = …


    CompositionLocalProvider(


    LocalColors provides rememberedColors,


    LocalTypography provides typography


    ) {


    MaterialTheme(


    colors = rememberedColors.toMaterialColors(),


    typography = typography.toMaterialTypography(),


    shapes = …,


    content = content


    )


    }


    }

    View Slide

  85. Wrap Material
    @Composable


    fun DigTheme(


    typography: Typography = DigTheme.typography,


    colors: Colors = DigTheme.colors,


    content: @Composable ()
    -
    >
    Unit


    ) {


    val rememberedColors = …


    CompositionLocalProvider(


    LocalColors provides rememberedColors,


    LocalTypography provides typography


    ) {


    MaterialTheme(


    colors = rememberedColors.toMaterialColors(),


    typography = typography.toMaterialTypography(),


    shapes = …,


    content = content


    )


    }


    }

    View Slide

  86. Wrap Material
    @Composable


    fun DigTheme(


    typography: Typography = DigTheme.typography,


    colors: Colors = DigTheme.colors,


    content: @Composable ()
    -
    >
    Unit


    ) {


    val rememberedColors = …


    CompositionLocalProvider(


    LocalColors provides rememberedColors,


    LocalTypography provides typography


    ) {


    MaterialTheme(


    colors = rememberedColors.toMaterialColors(),


    typography = typography.toMaterialTypography(),


    shapes = …,


    content = content


    )


    }


    }

    View Slide

  87. Wrap Material
    @Composable


    fun DigTheme(


    typography: Typography = DigTheme.typography,


    colors: Colors = DigTheme.colors,


    content: @Composable ()
    -
    >
    Unit


    ) {


    val rememberedColors = …


    CompositionLocalProvider(


    LocalColors provides rememberedColors,


    LocalTypography provides typography


    ) {


    MaterialTheme(


    colors = rememberedColors.toMaterialColors(),


    typography = typography.toMaterialTypography(),


    shapes = …,


    content = content


    )


    }


    }

    View Slide

  88. Wrap Material
    @Composable


    fun DigTheme(


    typography: Typography = DigTheme.typography,


    colors: Colors = DigTheme.colors,


    content: @Composable ()
    -
    >
    Unit


    ) {


    val rememberedColors = …


    CompositionLocalProvider(


    LocalColors provides rememberedColors,


    LocalTypography provides typography


    ) {


    MaterialTheme(


    colors = rememberedColors.toMaterialColors(),


    typography = typography.toMaterialTypography(),


    shapes = …,


    content = content


    )


    }


    }

    View Slide

  89. Wrap Material
    @Composable


    fun DigTheme(


    typography: Typography = DigTheme.typography,


    colors: Colors = DigTheme.colors,


    content: @Composable ()
    -
    >
    Unit


    ) {


    val rememberedColors = …


    CompositionLocalProvider(


    LocalColors provides rememberedColors,


    LocalTypography provides typography


    ) {


    MaterialTheme(


    colors = rememberedColors.toMaterialColors(),


    typography = typography.toMaterialTypography(),


    shapes = …,


    content = content


    )


    }


    }

    View Slide

  90. Wrap Material
    @Composable


    fun DigTheme(


    typography: Typography = DigTheme.typography,


    colors: Colors = DigTheme.colors,


    content: @Composable ()
    -
    >
    Unit


    ) {


    val rememberedColors = …


    CompositionLocalProvider(


    LocalColors provides rememberedColors,


    LocalTypography provides typography


    ) {


    MaterialTheme(


    colors = rememberedColors.toMaterialColors(),


    typography = typography.toMaterialTypography(),


    shapes = …,


    content = content


    )


    }


    }

    View Slide

  91. Wrap Material
    DigTheme {


    }

    View Slide

  92. Wrap Material
    DigTheme {


    Row {


    Button(onClick = { }) {


    Text("Button")


    }


    Button(


    onClick = { },


    enabled = false


    ) {


    Text("Disabled")


    }


    }


    }

    View Slide

  93. Wrap Material
    DigTheme {


    Row {


    Button(onClick = { }) {


    Text("Button")


    }


    Button(


    onClick = { },


    enabled = false


    ) {


    Text("Disabled")


    }


    }


    }

    View Slide

  94. Migration
    • Not all Material elements perform like we expect

    • Ex. TopAppBar uses accent color, we use surface color.

    • Mitigate by creating custom composables

    • Which implementation to use?

    View Slide

  95. Migration
    Button()
    TextButton()
    OutlinedButton()
    FloatingActionButton()
    Switch()
    Checkbox()
    RadioButton()
    Text()
    TextField()
    LinearProgressIndicator()
    Card()
    Button()
    TextButton()
    OutlinedButton()
    FloatingActionButton()
    Switch()
    Checkbox()
    RadioButton()
    Text()
    TextField()
    LinearProgressIndicator()
    Card()
    TopAppBar()
    NavigationBar()

    View Slide

  96. dependencies {


    api(libs.androidx.compose.ui)


    api(libs.androidx.compose.runtime)


    api(libs.androidx.compose.foundation)


    api(libs.androidx.compose.material)




    implementation(libs.androidx.activity.activity.compose)


    implementation(libs.androidx.compose.animation)


    implementation(libs.androidx.lifecycle.lifecycle.viewmodel.compose)


    implementation(libs.androidx.compose.ui.ui.tooling.preview)


    debugImplementation(libs.androidx.compose.ui.ui.tooling)


    }
    Migration

    View Slide

  97. dependencies {


    api(libs.androidx.compose.ui)


    api(libs.androidx.compose.runtime)


    api(libs.androidx.compose.foundation)


    api(libs.androidx.compose.material)




    implementation(libs.androidx.activity.activity.compose)


    implementation(libs.androidx.compose.animation)


    implementation(libs.androidx.lifecycle.lifecycle.viewmodel.compose)


    implementation(libs.androidx.compose.ui.ui.tooling.preview)


    debugImplementation(libs.androidx.compose.ui.ui.tooling)


    }
    dependencies {


    api(libs.androidx.compose.ui)


    api(libs.androidx.compose.runtime)


    api(libs.androidx.compose.foundation)


    implementation(libs.androidx.compose.material)


    implementation(libs.androidx.activity.activity.compose)


    implementation(libs.androidx.compose.animation)


    implementation(libs.androidx.lifecycle.lifecycle.viewmodel.compose)


    implementation(libs.androidx.compose.ui.ui.tooling.preview)


    debugImplementation(libs.androidx.compose.ui.ui.tooling)


    }
    Migration

    View Slide

  98. Progress
    Implement Theme Audit Components Remove Material

    View Slide

  99. Progress
    Implement Theme Audit Components Remove Material
    Audit

    View Slide

  100. Ryan Harter @RHARter
    Custom Design Systems
    in Jetpack Compose UI

    View Slide