---
title: Physical Layouts
toc_max_heading_level: 4
---

A physical layout is a devicetree entity that aggregates all details about a certain possible keyboard layout.
It contains:

- A [keyboard scan (kscan) driver](../../config/kscan.md)
- A [matrix transform](../../config/layout.md#matrix-transform)
- (Optional) [Physical key positions](#physical-layout-positions)

## Basic Physical Layout

A basic physical layout without the `keys` property looks like this:

```dts
/ {
    default_layout: default_layout {
        compatible = "zmk,physical-layout";
        display-name = "Default Layout";
        transform = <&default_transform>;
        kscan = <&kscan0>;
    };
};
```

It is given a name, a matrix transform, and a kscan. If all of your physical layouts share the same kscan, then the `kscan` property can be omitted - in this case it needs to be set in the [`chosen` node](./new-shield.mdx#chosen-node). See the [configuration section on physical layouts](../../config/index.md) for reference.

## (Optional) Keys Property

:::warning[Beta Feature]

ZMK Studio is in beta. Although every effort has been made to provide a stable experience, you may still encounter issues during use. Please report any issues to [GitHub Issues](https://github.com/zmkfirmware/zmk-studio/issues).

:::

The `keys` property is required for [ZMK Studio](../../features/studio.md) support. It is used to describe the physical attributes of each key position present in that layout. If this property is used, then you should define the physical layouts and any [position maps](#position-map) in a file called `<your keyboard>-layouts.dtsi`. This file should then be imported by the appropriate file, such as an `.overlay`, `.dts`, or a `.dtsi` (last of which is itself imported by one of the previous).

To pull in the necessary definition for creating physical layouts with the `keys` property, a new include should be added to the top of the devicetree file:

```
#include <physical_layouts.dtsi>
```

Assigned to the `keys` property is an array of key descriptions listed in the same order as keymap bindings, matrix transforms, etc.
A key description has the shape `<&key_physical_attrs w h x y r rx ry>` with the following properties:

| Property   | Type     | Description                          | Unit                                                    |
| ---------- | -------- | ------------------------------------ | ------------------------------------------------------- |
| Width      | int (>0) | Key(cap) width                       | [centi-](https://en.wikipedia.org/wiki/Centi-)"keyunit" |
| Height     | int (>0) | Key(cap) height                      | [centi-](https://en.wikipedia.org/wiki/Centi-)"keyunit" |
| X          | uint     | Key X position (top-left point)      | [centi-](https://en.wikipedia.org/wiki/Centi-)"keyunit" |
| Y          | uint     | Key Y position (top-left point)      | [centi-](https://en.wikipedia.org/wiki/Centi-)"keyunit" |
| Rotation   | int      | Key rotation (positive => clockwise) | [centi-](https://en.wikipedia.org/wiki/Centi-)degree    |
| Rotation X | int      | Rotation origin X position           | [centi-](https://en.wikipedia.org/wiki/Centi-)"keyunit" |
| Rotation Y | int      | Rotation origin Y position           | [centi-](https://en.wikipedia.org/wiki/Centi-)"keyunit" |

:::tip
You can specify negative values in devicetree using parentheses around it, e.g. `(-3000)` for a 30 degree counterclockwise rotation.
:::

### Physical Layout with Keys Example

Here is an example of a physical layout for a 2x2 macropad:

```dts
#include <physical_layouts.dtsi>

/ {
    macropad_physical_layout: macropad_physical_layout {
        compatible = "zmk,physical-layout";
        display-name = "Macro Pad";
        transform = <&default_transform>;
        kscan = <&kscan0>;
        keys  //                     w   h    x    y     rot    rx    ry
            = <&key_physical_attrs 100 100    0    0       0     0     0>
            , <&key_physical_attrs 100 100  100    0       0     0     0>
            , <&key_physical_attrs 100 100    0  100       0     0     0>
            , <&key_physical_attrs 100 100  100  100       0     0     0>
            ;
    };
};
```

## Using Predefined Layouts

ZMK defines a number of popular physical layouts in-tree at [`app/dts/layouts`](https://github.com/zmkfirmware/zmk/tree/main/app/dts/layouts).
To use such layouts, import them and assign their `transform` and (optionally) `kscan` properties.

Here is an example of using the predefined physical layouts for a keyboard with the same layout as the "ferris":

```dts
#include <layouts/cuddlykeyboards/ferris.dtsi>

// Assigning suitable kscan and matrix transforms
&cuddlykeyboards_ferris_layout {
    transform = <&default_transform>;
    kscan = <&kscan0>;
};
```

Shared physical layouts found in the same folder are defined such that they can be used together, to define [multiple physical layout](#multiple-physical-layouts) options. See below for more information on multiple physical layouts.

Here is an example of using the predefined physical layouts for a 60% keyboard:

```dts
#include <layouts/common/60percent/all1u.dtsi>
#include <layouts/common/60percent/ansi.dtsi>
#include <layouts/common/60percent/hhkb.dtsi>
#include <layouts/common/60percent/iso.dtsi>


// Assigning suitable kscan and matrix transforms
&layout_60_ansi {
    transform = <&ansi_transform>;
    kscan = <&ansi_kscan>;
};

&layout_60_iso {
    transform = <&iso_transform>;
    kscan = <&iso_kscan>;
};

&layout_60_all1u {
    transform = <&all_1u_transform>;
    kscan = <&all_1u_kscan>;
};

&layout_60_hhkb {
    transform = <&hhkb_transform>;
    kscan = <&hhkb_kscan>;
};
```

## Multiple Physical Layouts

If a keyboard has multiple possible layouts (e.g. you can snap off an outer column), then you should define multiple matrix transformations and multiple physical layouts, one for each possible layout.
If necessary, you can also define multiple kscan instances.

```dts
// Needed if and only if keys property is used
#include <physical_layouts.dtsi>

/ {
    default_layout: default_layout {
        compatible = "zmk,physical-layout";
        display-name = "Default Layout";
        transform = <&default_transform>;
        kscan = <&kscan0>;
        keys = <...>; // List of key positions, optional
    };

    alt_layout: alt_layout {
        compatible = "zmk,physical-layout";
        display-name = "Alternate Layout";
        transform = <&alt_transform>;
        kscan = <&alt_kscan0>;
        keys = <...>; // List of key positions, optional
    };
};
```

### Position Map

When switching between layouts using [ZMK Studio](../../features/studio.md), an attempt is made to automatically infer bindings for the keys in the new layout from the old layout. Keys with the same physical key properties are given the same binding. This approach has some limitations, so for more accurate transference of bindings a position map is used.

A position map looks something like this:

```dts
/ {
    position_map {
        compatible = "zmk,physical-layout-position-map";
        complete; // Optional, see 'Example non-complete position map'
        layout1: layout1 {
            physical-layout = <&physical_layout1>;
            positions = <...>; // List of positions to map
        };
        layout2: layout2 {
            physical-layout = <&physical_layout2>;
            positions = <...>; // List of positions to map
        };
        // Additional layout child nodes
    };
};
```

A child node is defined for every layout the keyboard can have. The `positions` properties each contain an array of indices, which are used to refer to keys in the `keys` array of their corresponding physical layout. A `0` in the `positions` property refers to the first key in the `keys` array, a `1` refers to the second, and so on.

When switching from one layout to another, say from layout 1 to layout 2, the _orderings_ found in the `positions` arrays are used. The first key in the `positions` array of layout 2 is given the binding assigned to the first key in the `positions` array of layout 1, the second key in the `positions` array of layout 2 is given the binding assigned to the second key in the `positions` array of layout 1, and so on.

The position map should be marked as `complete` if all desired binding transfers are defined within it. Otherwise, [ZMK Studio](../../features/studio.md) will continue to automatically determine assignments for keys not listed in the position map. See [this example non-complete position map](#example-non-complete-position-map) for why this could be useful.

See also the [configuration section on position maps](../../config/layout.md#physical-layout-position-map).

#### Writing a position map

Start by creating the parent node defining the position map:

```dts
/ {
    keypad_position_map {
        compatible = "zmk,physical-layout-position-map";
        complete; // Optional, see 'Example non-complete position map'

        // Child node 1 here

        // Child node 2 here

        // ...
    };
};
```

It is easiest to write the position map by considering one layout to be the "reference" layout, and defining all other position maps with respect to it. The reference layout should usually be the one with the most keys, as that creates a position map where no key bindings are lost when switching to a layout with fewer keys and back.

Create the child node for the reference layout, and fill the `positions` array by counting upwards, giving it the same order and number of keys as the `keys` property of its physical layout. For a 2x2 macropad the child node would be

```dts
/ {
    keypad_position_map {
        // Other properties

        macropad_map: macropad {
            physical-layout = <&macropad_layout>;
            positions // This is equivalent to `positions = <0 1 2 3>;`, reshaped for readability
                = < 0  1 >
                , < 2  3 >;
        };
    };
};
```

Next write the child nodes for every other layout with respect to the reference. For keys present in the reference which aren't present in the layout you're writing the child node for, count backwards: The first key that isn't present is given the highest number found in the `positions` array of the reference, the second key that isn't present is given the second highest number, and so on. The below examples show this process more clearly.

#### Example larger position map

Consider the following macropad/numpad with two physical layouts:

![A 4x5 numpad/macropad](../../assets/hardware-integration/numpad.svg)

Let us first consider each side individually. The "reference" position map of the left side would look like this:

```dts
/ {
    keypad_position_map {
        // Other properties

        macropad_map: macropad {
            physical-layout = <&macropad_layout>;
            positions
                = < 0  1  2  3>
                , < 4  5  6  7>
                , < 8  9 10 11>
                , <12 13 14 15>
                , <16 17 18 19>;
        };
    };
};
```

Meanwhile, the "reference" position map of the right side with fewer keys would look like this:

```dts
/ {
    keypad_position_map {
        // Other properties

        numpad_map: numpad {
            physical-layout = <&numpad_layout>;
            positions
                = < 0  1  2  3>
                , < 4  5  6  7>
                , < 8  9 10   >
                , <11 12 13 14>
                , <15    16   >;
        };
    };
};
```

As a reminder, the `positions` property is a one-dimensional array like the `keys` property, formatted nicely through the use of whitespace and angle bracket groupings.

If the left side with more keys was used as the reference layout, then the overall position map of the keyboard would look like this:

```dts
/ {
    keypad_lossless_position_map {
        compatible = "zmk,physical-layout-position-map";
        complete;

        macropad_map: macropad {
            physical-layout = <&macropad_layout>;
            positions
                = < 0  1  2  3>
                , < 4  5  6  7>
                , < 8  9 10 11>
                , <12 13 14 15>
                , <16 17 18 19>;
        };

        numpad_map: numpad {
            physical-layout = <&numpad_layout>;
            positions
                = < 0  1  2  3>
                , < 4  5  6  7>
                , < 8  9 10 19>
                , <11 12 13 14>
                , <15 18 16 17>;
        };
    };
};
```

The "missing" positions are filled with the "spare" numbers of the layout with more keys. The order in which the spare keys are used is arbitrary and counting backwards is a convenient way to assign them.

If the right side with fewer keys were used as a reference instead, then the overall position map would look like this:

```dts
/ {
    keypad_lossy_position_map {
        compatible = "zmk,physical-layout-position-map";
        complete;

        macropad_map: macropad {
            physical-layout = <&macropad_layout>;
            positions
                = < 0  1  2  3>
                , < 4  5  6  7>
                , < 8  9 10   >
                , <12 13 14 15>
                , <16    18   >;
        };

        numpad_map: numpad {
            physical-layout = <&numpad_layout>;
            positions
                = < 0  1  2  3>
                , < 4  5  6  7>
                , < 8  9 10   >
                , <11 12 13 14>
                , <15    16   >;
        };
    };
};
```

The above example is "lossy" because (unlike the previous "lossless" example) if a user switches from the macropad layout to the numpad layout _and then_ switches from the numpad layout back to the macropad layout, the assignments to the keys present but not listed in the macropad's map are lost.

#### Example non-`complete` position map

Consider the above device again -- most of the positions have identical `keys` properties. For example, the macropad's `12` key and the numpad's `11` key would have the same physical property, and be mapped to each other automatically. The keys whose mappings are unable to be determined automatically are those with different physical characteristics: the 2u high and 2u wide keys, and their corresponding 1u counterparts.

A non-`complete` position map can be used to assign mappings to only these particular keys:

```dts
/ {
    keypad_lossy_position_map {
        compatible = "zmk,physical-layout-position-map";

        macropad_map: macropad {
            physical-layout = <&macropad_layout>;
            positions = <7 15 16>;
        };

        numpad_map: numpad {
            physical-layout = <&numpad_layout>;
            positions = <7 14 15>;
        };
    };
};
```

This is noticably simpler to write, and can be a useful way of saving flash space for memory-constrained devices. The above is a "lossy" mapping, though. While "lossless" non-`complete` mappings are possible, they can be counter-intuitive enough that it may be easier to write the full position map instead.

For completeness, the equivalent "lossless" non-`complete` position map is shown below:

```dts
/ {
    keypad_lossy_position_map {
        compatible = "zmk,physical-layout-position-map";

        macropad_map: macropad {
            physical-layout = <&macropad_layout>;
            positions = <7 11 15 19 16 17>;
        };

        numpad_map: numpad {
            physical-layout = <&numpad_layout>;
            positions = <7 19 14 18 15 17>;
        };
    };
};
```

#### Additional example: corne

The following is an example of a "lossless" position map which maps the 5-column and 6-column Corne keymap layouts. The 6 column layout is the reference layout.

```dts
    foostan_corne_lossless_position_map {
        compatible = "zmk,physical-layout-position-map";

        complete;

        twelve_map: twelve {
            physical-layout = <&foostan_corne_6col_layout>;
            positions
                = < 0  1  2  3  4  5  6  7  8  9 10 11>
                , <12 13 14 15 16 17 18 19 20 21 22 23>
                , <24 25 26 27 28 29 30 31 32 33 34 35>
                , <         36 37 38 39 40 41         >;
        };

        ten_map: ten {
            physical-layout = <&foostan_corne_5col_layout>;
            positions
                = <41  0  1  2  3  4  5  6  7  8  9 40>
                , <39 10 11 12 13 14 15 16 17 18 19 38>
                , <37 20 21 22 23 24 25 26 27 28 29 36>
                , <         30 31 32 33 34 35         >;
        };
    };
```

Meanwhile, the "lossy" version of the same position map with the 5 column version as reference looks like this:

```dts
    foostan_corne_lossy_position_map {
        compatible = "zmk,physical-layout-position-map";

        complete;

        twelve_map: twelve {
            physical-layout = <&foostan_corne_6col_layout>;
            positions
                = < 1  2  3  4  5  6  7  8  9 10>
                , <13 14 15 16 17 18 19 20 21 22>
                , <25 26 27 28 29 30 31 32 33 34>
                , <      36 37 38 39 40 41      >;
        };

        ten_map: ten {
            physical-layout = <&foostan_corne_5col_layout>;
            positions
                = < 0  1  2  3  4  5  6  7  8  9>
                , <10 11 12 13 14 15 16 17 18 19>
                , <20 21 22 23 24 25 26 27 28 29>
                , <      30 31 32 33 34 35      >;
        };
    };
```
