a.bit_fld is only 3 bits big, it can't store the value 0x10. Behavior is implementation-defined, but in this case it has probably stored 0.
Then 1 << 2 is binary 100 as you say. Assuming we did store 0 at the first step, the result of ( a.bit_fld | (1<<2)) is an int with value 4 (binary 100).
In a signed 2's complement 3-bit representation, this bit pattern represents the value -4, so it's not at all surprising if -4 is what you get when you store the value 4 to a.bit_fld, although again this is implementation-defined.
In the printf, a.bit_fld is promoted to int before passing it as a vararg. The 2's complement 32 bit representation of -4 is 0xfffffffc, which is what you see.
It's also undefined behavior to pass an int instead of an unsigned int to printf for the %x format. It's not surprising that it appears to work, though: for varargs in general there are certain circumstances where it's valid to pass an int and read it as an unsigned int. printf isn't one of them, but an implementation isn't going to go out of its way to stop it appearing to work.
bit_fldis0000100band not0001000b.gcc -Wallwould say:bitfield.c:9: warning: overflow in implicit constant conversion.