The problem is that the plot panel does not have defined dimensions until drawing ("NULL unit"), but your legend guide has. See also npc coordinates of geom_point in ggplot2 or figuring out panel size given dimensions for the final plot in ggsave (to show count for geom_dotplot). I think it will be extremely tricky to draw the legend guide in the exact same size as your panel.
However, you can make use of a trick when dealing with complex legend formatting: Create a fake legend. The challenge here is to adjust the fill scale to perfectly match the range of your plot (which is not usually exactly the range of your data values). The rest is just a bit of R semantics. Some important comments in the code.
library(ggplot2)
corrs <- structure(list(Var1 = structure(c(1L, 2L, 3L, 1L, 2L, 3L, 1L, 2L, 3L), levels = c("Var1", "Var2", "Var3"), class = "factor"), Var2 = structure(c(1L, 1L, 1L, 2L, 2L, 2L, 3L, 3L, 3L), levels = c("Var1", "Var2", "Var3"), class = "factor"), value = c(1, -0.11814395012334, -0.91732952510938, -0.969618394505233, 1, -0.00122085912153125, -0.191116513684392, -0.0373711776919663, 1)), class = "data.frame", row.names = c(NA, -9L))
## to set the scale properly, you will need to set limits and breaks,
## I am doing this semi-automatically
range_fill <- range(corrs$value)
lim_fill <- c(floor(range_fill[1]), ceiling(range_fill[2]))
## len = 5 and round to 2 is hard coded, depending on the scale breaks that you want
breaks_fill <- round(seq(lim_fill[1], lim_fill[2], len = 5), 2)
## need to rescale the fill to the range of you y values,
## so that your fill scale correctly corresponds to the range of your y
## however, the actual range of your plot depends if you're in a discrete or continuous range.
## here in a discrete range
lim_y <- range(as.integer(corrs$Var2))
lim_x <- range(as.integer(corrs$Var1))
lim_vals <- lim_y + c(-.5, .5)
## actual rescaling happens here
new_y <- scales::rescale(breaks_fill, lim_vals)
## the position and width of the color bar are defined with
scl_x <- lim_x[2] + .7 # the constant is hard coded
scl_xend <- scl_x + .2 # also hard coded
## make a data frame for the segments to be created
## using approx so that we have many little segments
approx_fill <- approx(new_y, breaks_fill, n = 1000)
df_seg <- data.frame(y = approx_fill$x, color = approx_fill$y)
## data frame for labels, xend position being hard coded
df_lab <- data.frame(y = new_y, x = scl_xend + .1, label = breaks_fill)
## data frame for separators
sep_len <- .05
df_sep <- data.frame(
y = new_y, yend = new_y,
x = rep(c(scl_x, scl_xend - sep_len), each = 5),
xend = rep(c(scl_x + sep_len, scl_xend), each = 5)
)
ggplot(corrs) +
geom_tile(aes(x = Var1, y = Var2, fill = value)) +
geom_segment(
data = df_seg,
aes(x = scl_x, xend = scl_xend, y = y, yend = y, color = color)
) +
## now the labels, the size being hard coded
geom_text(data = df_lab, aes(x, y, label = label), size = 9 * 5 / 14) +
## now make the white little separators
geom_segment(
data = df_sep, aes(x = x, xend = xend, y = y, yend = yend),
color = "white"
) +
## set both color and fill scales exactly
scale_fill_continuous(limits = lim_fill, breaks = breaks_fill) +
scale_color_continuous(limits = lim_fill, breaks = breaks_fill) +
## turn off coordinate clipping and limit panel to data area)
coord_cartesian(xlim = lim_x, ylim = lim_y, clip = "off") +
## last but not least remove the other legends and add a bit of margin
theme(
legend.position = "none",
plot.margin = margin(r = 1, unit = "in")
)

Created on 2022-12-29 with reprex v2.0.2