Skip to content

Conversation

@awkannan
Copy link
Contributor

@awkannan awkannan commented Nov 20, 2025

Right now, ZMK Studio will show a blank key on keys that have any behavior set other than Key Press.

So at a glance, there isn't an easy way to differentiate between truly unbound keys, transparent keys, and keys with other behaviors assigned, such as studio unlock, bluetooth configuration, layer tap, mod tap, etc.

In order to see the behavior name, you have to hover over the key. This isn't ideal for a number of reasons.

This change adds key headers to keys at rest, without hover state.

There is one key challenge though - which is the space. If the header overflows the key, the UI will look like a mess. To handle this, I implemented a very simple heuristic (which should probably be discussed more).

If there are 10 characters or more, we try to split the behavior name into separate words (separated by a space or -) and then shorten each part equally to fit into 9 characters. This should handle most well formed behavior names, which are usually 2-3 words long.

If the human readable name of the behavior is 9 characters or less, we assume it'll fit on the key, and just display it as is.

Here's a few examples of what it looks like:
image
image

This meant to be up for discussion, this is just a starting point.

@netlify
Copy link

netlify bot commented Nov 20, 2025

Deploy Preview for zmk-studio ready!

Name Link
🔨 Latest commit 46acf08
🔍 Latest deploy log https://app.netlify.com/projects/zmk-studio/deploys/69274994c634a40008d82d3f
😎 Deploy Preview https://deploy-preview-157.preview.zmk.studio
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@awkannan awkannan force-pushed the 202511_add_header_no_hover branch from 2a4be70 to 02ac8f4 Compare November 20, 2025 01:07
@awkannan
Copy link
Contributor Author

Note -

I think that maybe we can special-case Key Press or other behaviors that get special treatment.
For example, maybe we hide the headers on Key Press
Or create a list of short name overrides for certain behavior strings, etc.

I think all of these things might improve the experience, but we have to start somewhere.

I don't know what the community will land on, so I'm starting with something simple.

@awkannan
Copy link
Contributor Author

I decided to be a little opinionated - and hid the Key Press header via special case.
This will clean up some of the visual noise.
Key Press seems to be a reasonable "default" behavior, so I think it can be hidden.

image

I don't think we should implement any other special cases at this point, though.

@danielsvane
Copy link
Contributor

I think its an improvement in most cases. The header when hovering should have the same limitation I think, since it will take up the same relative space:

image image

Maybe we could have a list of common shorthands like:

Backspace Shift: bksp shft
Transparent: trans
Bootloader: bootldr

And then fall back to your auto shortening logic if we dont have a common shorthand. Its not like Im in love with this solution, but I cant think of a perfect way to do this. We can also decrease the font size of the header a bit if that will help.

@danielsvane
Copy link
Contributor

danielsvane commented Nov 20, 2025

To add to the list of silly ideas, we can scale down texts that are too long to fit in the container:

image image

I actually think this looks decent enough, but would like some input.

@awkannan
Copy link
Contributor Author

I think ultimately having short names and/or icons in metadata is probably the best solution.

Defining overrides now could mimic the same thing that the ultimate solution would bring - so I would be in favor of implementing overrides now with the eventual plan of relying on metadata instead of an override mapping in the future.

In the meantime - I think I prefer the shortening vs the scaling. I worry with a too long behavior name, the scaling could make the text too small to read, especially on smaller screens. In your example, that looks good imo, but I think it also will limit the legible range of the headers.

On the flip side - some keys do have more space for the normal size text. Not all keys are 1U. The scaling solution would handle this case elegantly. Another option would be to adjust the max length of a shortened header based on key size - but that also adds complexity.

I'm leaning towards defining set overrides for now and using the existing shortening heuristic for anything without an override. We can define overrides for all of the standard ZMK behaviors.

In the future, the metadata can provide the short header we should use, and it'd be up to module developers to provide reasonable short names.

Thoughts?

@danielsvane
Copy link
Contributor

danielsvane commented Nov 20, 2025

I think the lookup table for short names is the easiest to maintain and implement, especially if its only a short term solution. I dont think the short names will be too confusing for users, since they are probably already nerds. Do you have a way of getting all the standard behaviors?

I personally wouldnt mind having keypress be listed as &kp

>
<div className={`absolute text-xs ${selected ? "text-primary-content" : "z1text-base-content"} opacity-0 group-hover:opacity-80 top-1 text-nowrap left-1/2 font-light -translate-x-1/2 text-center transition-opacity`}>{header}</div>
<div className={`absolute text-xs ${selected ? "text-primary-content" : "z1text-base-content"} opacity-80 group-hover:opacity-0 top-1 text-nowrap left-1/2 font-light -translate-x-1/2 text-center transition-opacity`}>{shortenHeader(header)}</div>
<div className={`absolute text-xs ${selected ? "text-primary-content" : "z1text-base-content"} opacity-0 group-hover:opacity-80 top-1 text-nowrap left-1/2 font-light -translate-x-1/2 text-center transition-opacity`}>{shortenHeader(header)}</div>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One of these has to be redundant right? If we show the same text on the hovered keys, we dont need to fade out the small version, and fade in the big version

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! I fixed this.

@awkannan
Copy link
Contributor Author

I think the lookup table for short names is the easiest to maintain and implement, especially if its only a short term solution. I dont think the short names will be too confusing for users, since they are probably already nerds. Do you have a way of getting all the standard behaviors?

I personally wouldnt mind having keypress be listed as &kp

I implemented the lookup table - plus some boilerplate for potential icons.
I can strip out the icon boilerplate to make this cleaner as well.

I think having short names be as human friendly as possible would be ideal, and I took a stab at defining some short names along those lines. &kp makes sense if you've looked at the keymap files, but if you've only ever used Studio with ZMK / never looked at the code, it might not make as much sense.

For all standard behaviors - I compiled some firmware without removing any behaviors and made short names for those. I'll go through the ZMK docs + code to see what else I need to add. We're missing a bunch (mouse emulation, backlight, soft off, underglow, sensor rotation) that I will try to add.

@danielsvane
Copy link
Contributor

Okay good job man, I think the shorthand names you selected are fine. I made an attempt at adding icons for the actual key values here #142. I dont think the solution is perfect, but I think it adds a lot visually using icons

@awkannan awkannan force-pushed the 202511_add_header_no_hover branch from 59c5e6e to 24279ab Compare November 26, 2025 03:30
Copy link
Collaborator

@petejohanson petejohanson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This definitely feels like a step in the right direction, but a few of the abbreviations I'm still on the fence on... How my base layer looks on my Blank Slate w/ the preview:

image

if(typeof header === "undefined"){
return "";
}
const maxHeaderLength = 9;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I personally don't love constants hiding in functions, Can we pull this out so it's easier to find, and maybe capitalize, e.g. MAX_HEADER_LENGTH ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call! Done!

return "";
}
const maxHeaderLength = 9;
if(shortNames[header]?.short != null){
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if(shortNames[header]?.short != null){
if(shortNames[header]?.short){

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Empty string evaluates as falsy in JS - so I wanted to make this a null check, since we use empty string to define the "blank" override for "Key Press"

I get why this came up though, since it's not obvious at first glance, and is really for a special edge case just for Key Press.

Any suggestions as to best way to fix?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, fair. Ignore me then.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps better to be !== which is a bit more explicit, and should work?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or at least a comment that this is a null check explicitly so empty strings are allowed as short names.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep - that should work. I think the != was a relic from when I didn't have an explicit undefined check above

Copy link
Contributor Author

@awkannan awkannan Nov 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, ignore me. The undefined check above is for the input header.
We need to use != since shortNames[header]?.short can be undefined.

I think a comment is most appropriate here. I got it wrong even though I implemented it initially

EDIT: I made this clearer by disallowing null in the typing. This makes the logic easier to understand, but I left a comment anyways

"Grave/Escape": {"short": "Grv/Esc"},
"Key Repeat": {"short": "Key Rept"},
"Key Toggle": {"short": "Key Togg"},
"Momentary Layer": {"short": "MomntLayr"},
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I love this one.... Maybe:

Suggested change
"Momentary Layer": {"short": "MomntLayr"},
"Momentary Layer": {"short": "MLayer"},

"Momentary Layer": {"short": "MomntLayr"},
"Output Selection": {"short": "OutputSel"},
"Sticky Key": {"short": "Stcky Key"},
"Studio Unlock": {"short": "StdioUnlk"},
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"Studio Unlock": {"short": "StdioUnlk"},
"Studio Unlock": {"short": "Unlock"},

"Sticky Key": {"short": "Stcky Key"},
"Studio Unlock": {"short": "Unlock"},
"Toggle Layer": {"short": "Togg Layr"},
"Transparent": {"short": "Trans"}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, last nit: Since we've got room, maybe this could be:

Suggested change
"Transparent": {"short": "Trans"}
"Transparent": {"short": "Transpnt"}

Copy link
Contributor Author

@awkannan awkannan Nov 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about we drop all the vowels?
Trnsprnt
We also have room for
Transprnt
if we go by the 9 character max

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WFM.

Copy link
Collaborator

@petejohanson petejohanson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, but can you please squash the commits into one proper feat commit, then we can merge?

@awkannan awkannan force-pushed the 202511_add_header_no_hover branch from 096e67c to 4f74f40 Compare November 26, 2025 18:39
Defines short names for behaviors for display on key headers
@awkannan awkannan force-pushed the 202511_add_header_no_hover branch from 4f74f40 to 46acf08 Compare November 26, 2025 18:40
@awkannan
Copy link
Contributor Author

Should be good to go now!

@petejohanson petejohanson merged commit 0f965ed into zmkfirmware:main Nov 26, 2025
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants