Your problem is that #1 is the value of a key, not the key name, even in the unknown handler.
The following works better:
\documentclass{article} \usepackage{xcolor} \ExplSyntaxOn \keys_define:nn { skyrmion / mwe } { color .tl_set:N = \l__skyrmion_color_tl, color .initial:n = black, text .tl_set:N = \l__skyrmion_text_tl, unknown .code:n = \tl_set_eq:NN \l__skyrmion_color_tl \l_keys_key_tl } \NewDocumentCommand \skyrmion { O{} } { \keys_set:nn { skyrmion / mwe } {#1} \textcolor {\l__skyrmion_color_tl} {\l__skyrmion_text_tl} } \ExplSyntaxOff \begin{document} \skyrmion[color = red, text = {Hello, \LaTeX}] \skyrmion[red, text = {Hello, \LaTeX}] \end{document}
However, this is still not foolproof, because you only want to treat keys that didn't get any value this way (now a \skyrmion[foo=bar] would try to use the colour foo, which is obviously wrong). So we need to also check that no value was given:
\documentclass{article} \usepackage{xcolor} \ExplSyntaxOn \msg_new:nnn { skyrmion / mwe } { unknown-key } { The~ key~ \l_keys_key_str\c_space_tl is~ unknown~ and~ is~ being~ ignored. } \keys_define:nn { skyrmion / mwe } { color .tl_set:N = \l__skyrmion_color_tl, color .initial:n = black, text .tl_set:N = \l__skyrmion_text_tl, unknown .code:n = \tl_if_novalue:nTF {#1} { \tl_set_eq:NN \l__skyrmion_color_tl \l_keys_key_tl } { \msg_error:nn { skyrmion / mwe } { unknown-key } } ,unknown .default:V = \c_novalue_tl } \NewDocumentCommand \skyrmion { O{} } { \keys_set:nn { skyrmion / mwe } {#1} \textcolor {\l__skyrmion_color_tl} {\l__skyrmion_text_tl} } \ExplSyntaxOff \begin{document} \skyrmion[color = red, text = {Hello, \LaTeX}] \skyrmion[red, text = {Hello, \LaTeX}] \end{document}
This however doesn't throw any error on unknown keys. TikZ has a mechanism that determines whether something might be a colour which we can implement ourselves as well. We assume that everything that's a named colour is a colour (duh!) and that everything that contains an ! is a colour expression.
\documentclass{article} \usepackage{xcolor} \ExplSyntaxOn \msg_new:nnn { skyrmion / mwe } { unknown-key } { The~ key~ \l_keys_key_str\c_space_tl is~ unknown~ and~ is~ being~ ignored. } \keys_define:nn { skyrmion / mwe } { color .tl_set:N = \l__skyrmion_color_tl, color .initial:n = black, text .tl_set:N = \l__skyrmion_text_tl, unknown .code:n = \tl_if_novalue:nTF {#1} { \__skyrmion_if_colour:VTF \l_keys_key_str { \tl_set_eq:NN \l__skyrmion_color_tl \l_keys_key_tl } { \msg_error:nn { skyrmion / mwe } { unknown-key } } } { \msg_error:nn { skyrmion / mwe } { unknown-key } } ,unknown .default:V = \c_novalue_tl } \prg_new_protected_conditional:Npnn \__skyrmion_if_colour:n #1 { TF } { \cs_if_exist:cTF { \c_backslash_str color @ #1 } { \prg_return_true: } { \tl_if_in:nnTF {#1} { ! } \prg_return_true: \prg_return_false: } } \prg_generate_conditional_variant:Nnn \__skyrmion_if_colour:n { V } { TF } \NewDocumentCommand \skyrmion { O{} } { \keys_set:nn { skyrmion / mwe } {#1} \textcolor {\l__skyrmion_color_tl} {\l__skyrmion_text_tl} } \ExplSyntaxOff \begin{document} \skyrmion[color = red, text = {Hello, \LaTeX}] \skyrmion[red, text = {Hello, \LaTeX}] \end{document}
Shameless advertising: Doing the same in expkv (or expkv-def -- disclaimer: I'm the author of these) is a bit easier because in expkv you don't have to test for an unknown key not having a value, it differentiates between the two (in expkv there is a concept separating keys in NoVal-keys and Val-keys). So there this MWE would look like:
\documentclass{article} \usepackage{xcolor,expkv-def} \makeatletter \ekvdefinekeys{skyrmion/mwe} { store text = \skyrmion@text ,store color = \skyrmion@color ,initial color = black % #2 is the undetokenized key name of an unknown NoVal-key ,unknown noval = \edef\skyrmion@color{\unexpanded{#2}} } \NewDocumentCommand \skyrmion { O{} } {% \ekvset{skyrmion/mwe}{#1}% \textcolor{\skyrmion@color}{\skyrmion@text}% } \makeatother \begin{document} \skyrmion[color = red, text = {Hello, \LaTeX}] \skyrmion[red, text = {Hello, \LaTeX}] \end{document}
I used \makeatletter...\makeatother instead of L3-syntax here because expkv is not part of L3, and while store is 100% compatible with .tl_set:N this is sort of an implementation detail and nothing I can document, so to avoid any potential (however unlikely) conflict in the future, this doesn't rely on that fact.
Another variant using expkv-cs instead (because this MWE is really simple and defining the macro using it is kind of straight forward):
\documentclass{article} \usepackage{xcolor,expkv-cs} \makeatletter \ekvcSplit\skyrmion@kv { color = black ,text = } {\textcolor{#1}{#2}} \ekvdefunknownNoVal{\string\skyrmion@kv}{\ekvcPass\skyrmion@kv{color}{#2}} % only necessary for the optional argument grabbing \NewDocumentCommand \skyrmion { O{} } {\skyrmion@kv{#1}} \makeatother \begin{document} \skyrmion[color = red, text = {Hello, \LaTeX}] \skyrmion[red, text = {Hello, \LaTeX}] \end{document}