# How to Implement Neumorphic Theming with Custom Colors and Borders in Android Keyboard Views

> Implement custom neumorphic theming in Android keyboard views using custom colors and borders. Learn how to enable dynamic shadows and switch between soft-raised and solid keys.

- Repository: [Kazu/japanesekeyboard](https://github.com/kazumaproject/japanesekeyboard)
- Tags: tutorial
- Published: 2026-03-05

---

**Enable `themeMode = "custom"` and toggle `customBorderEnable` to switch between soft-raised neumorphic keys with dynamic shadows and traditional solid-color keys with borders.**

The JapaneseKeyboard project implements a flexible theming system that renders keys with a tactile, three-dimensional appearance using dynamically generated drawables. By configuring user preferences and leveraging layered drawables in [`FlickKeyboardView.kt`](https://github.com/kazumaproject/japanesekeyboard/blob/main/FlickKeyboardView.kt), you can apply neumorphic theming with custom colors and borders across the entire keyboard view. The implementation centers on the `getDynamicNeumorphDrawable()` method, which constructs highlight and shadow layers mathematically derived from your base color choices.

## Enable Custom Theme Mode

To activate custom styling capabilities, you must first set the keyboard's theme mode to custom. In [`FlickKeyboardView.kt`](https://github.com/kazumaproject/japanesekeyboard/blob/main/FlickKeyboardView.kt), the view checks the `themeMode` preference and enters a dedicated branch when the value equals `"custom"`.

```kotlin
when (themeMode) {
    "custom" -> {
        // Custom color and border logic executes here
    }
}

```

This trigger activates the color picker preferences and exposes the border toggle controls, allowing the subsequent logic to reference your custom color values instead of the default theme attributes.

## Configure Base Colors and Border Behavior

The system reads five distinct color values from user preferences to construct the key appearance. **Bold** parameters include `customKeyColor`, `customKeyTextColor`, `customSpecialKeyColor`, `customSpecialKeyTextColor`, and `customBorderColor`. The boolean flag `customBorderEnable` determines which rendering path the view takes.

- When `customBorderEnable` is **true**, the view draws a solid background color with an explicit border stroke, bypassing neumorphic effects entirely.
- When `customBorderEnable` is **false**, the view builds a composite `LayerDrawable` consisting of a neumorphic base layer and a transparent text overlay.

## Build the Dynamic Neumorphic Drawable

The core visual effect originates in `getDynamicNeumorphDrawable()` within [`FlickKeyboardView.kt`](https://github.com/kazumaproject/japanesekeyboard/blob/main/FlickKeyboardView.kt). This method programmatically generates a three-layer drawable that simulates physical depth through strategic color manipulation and positioning.

The method calculates derivative colors from your base input:

- **Highlight color**: Base color multiplied by `1.2f` (20% brighter) for the top-left light source
- **Shadow color**: Base color multiplied by `0.8f` (20% darker) for the bottom-right shadow
- **Surface color**: The original base color for the central key face

```kotlin
private fun getDynamicNeumorphDrawable(baseColor: Int, radius: Float): Drawable {
    val highlightColor = manipulateColor(baseColor, 1.2f)
    val shadowColor = manipulateColor(baseColor, 0.8f)
    val offset = dpToPx(4)
    val padding = dpToPx(2)

    val shadowDrawable = GradientDrawable().apply {
        shape = GradientDrawable.RECTANGLE
        cornerRadius = radius
        setColor(shadowColor)
    }
    
    val highlightDrawable = GradientDrawable().apply {
        shape = GradientDrawable.RECTANGLE
        cornerRadius = radius
        setColor(highlightColor)
    }
    
    val surfaceDrawable = GradientDrawable().apply {
        shape = GradientDrawable.RECTANGLE
        cornerRadius = radius
        setColor(baseColor)
    }

    return LayerDrawable(arrayOf(shadowDrawable, highlightDrawable, surfaceDrawable)).apply {
        setLayerInset(0, offset, offset, 0, 0)      // Shadow offset
        setLayerInset(1, 0, 0, offset, offset)      // Highlight offset
        setLayerInset(2, padding, padding, padding, padding) // Surface inset
    }
}

```

The `radius` parameter accepts `commonCornerRadius`, allowing you to maintain consistent corner roundness across all keys.

## Compose the Layered Background

For the full neumorphic effect, the view combines the dynamic drawable with a `SegmentedBackgroundDrawable` instance. This transparent overlay handles text rendering and flick-direction highlights without obscuring the shadow layers beneath.

```kotlin
val neumorphDrawable = getDynamicNeumorphDrawable(
    baseColor = targetBaseColor,
    radius = commonCornerRadius
)

val segmentedDrawable = SegmentedBackgroundDrawable(
    label = "",
    baseColor = Color.TRANSPARENT,
    highlightColor = targetHighlightColor,
    textColor = targetTextColor,
    cornerRadius = commonCornerRadius
)

val layerDrawable = LayerDrawable(arrayOf(neumorphDrawable, segmentedDrawable))

// Apply 2dp inset to align with QWERTY key spacing
val inset = dpToPx(2)
layerDrawable.setLayerInset(1, inset, inset, inset, inset)
background = layerDrawable

```

The `2dp` inset ensures the neumorphic layer properly aligns with the standard QWERTY keyboard layout spacing, preventing visual misalignment between adjacent keys.

## Implement Solid Border Keys

When users prefer traditional flat styling with defined edges, set `customBorderEnable` to `true`. This path bypasses the layered drawable construction and applies colors directly using view methods.

```kotlin
setDrawableSolidColor(customKeyColor)
setTextColor(customKeyTextColor)
setBorder(customBorderColor, borderWidth)

```

This approach uses `setBorder()` to apply the `customBorderColor` preference as a stroke around the key, creating a clean, minimalist appearance without depth simulation.

## Summary

- Set `themeMode = "custom"` in keyboard preferences to enable custom color theming in [`FlickKeyboardView.kt`](https://github.com/kazumaproject/japanesekeyboard/blob/main/FlickKeyboardView.kt)
- Use `customBorderEnable` to toggle between solid borders (`true`) and neumorphic depth effects (`false`)
- The `getDynamicNeumorphDrawable()` method generates three-layer drawables with mathematically calculated highlight (`×1.2`) and shadow (`×0.8`) colors
- Apply a `2dp` layer inset when composing the `LayerDrawable` to maintain proper key spacing
- Reference `customKeyColor`, `customSpecialKeyColor`, and other preference values to personalize both standard and special function keys

## Frequently Asked Questions

### How do I adjust the intensity of the neumorphic shadow effect?

Modify the multiplier values passed to `manipulateColor()` inside `getDynamicNeumorphDrawable()`. The JapaneseKeyboard source uses `1.2f` for highlights and `0.8f` for shadows by default; increasing the highlight multiplier above `1.2f` or decreasing the shadow multiplier below `0.8f` amplifies the contrast and perceived depth.

### Can neumorphic theming work with dark mode color schemes?

Yes, the dynamic calculation method adapts to any base color value. When a dark color serves as `customKeyColor`, the highlight becomes a lighter variant of that dark tone while the shadow becomes deeper, maintaining the soft-raised appearance across both light and dark themes according to the [`FlickKeyboardView.kt`](https://github.com/kazumaproject/japanesekeyboard/blob/main/FlickKeyboardView.kt) implementation.

### What is the purpose of SegmentedBackgroundDrawable in this implementation?

`SegmentedBackgroundDrawable` functions as a transparent overlay that paints the key label text and optional flick-direction indicators on top of the neumorphic base layer. It resides in [`SegmentedBackgroundDrawable.kt`](https://github.com/kazumaproject/japanesekeyboard/blob/main/SegmentedBackgroundDrawable.kt) and accepts `Color.TRANSPARENT` as its base fill, ensuring the underlying shadow and highlight layers remain visible while text renders crisply above them.

### Why is a 2dp inset required when applying the neumorphic drawable?

The inset creates necessary padding between the edge of the key view and the neumorphic effect itself. Without this spacing, the shadow and highlight layers would bleed directly to the view boundaries, causing adjacent keys to visually collide. The `2dp` value aligns the drawable with the QWERTY layout grid used throughout the JapaneseKeyboard project.