To my disappointment, this does not seem easy. The tableGrob function calls makeTableGrobs to place the grid object and returns the fully calculated gTree structure. It would be nice if you could intercept this, change some properties and continue; unfortunately, the drawing is done using gridExtra:::drawDetails.table , and this function insists on calling makeTableGrobs again, essentially killing any configuration option.
But this is not impossible. Basically, we can create our own version of drawDetails.table , which does not perform processing. Here is a function from gridExtra with one if added at the beginning.
drawDetails.table <- function (x, recording = TRUE) { lg <- if(!is.null(x$lg)) { x$lg } else { with(x, gridExtra:::makeTableGrobs(as.character(as.matrix(d)), rows, cols, NROW(d), NCOL(d), parse, row.just = row.just, col.just = col.just, core.just = core.just, equal.width = equal.width, equal.height = equal.height, gpar.coretext = gpar.coretext, gpar.coltext = gpar.coltext, gpar.rowtext = gpar.rowtext, h.odd.alpha = h.odd.alpha, h.even.alpha = h.even.alpha, v.odd.alpha = v.odd.alpha, v.even.alpha = v.even.alpha, gpar.corefill = gpar.corefill, gpar.rowfill = gpar.rowfill, gpar.colfill = gpar.colfill)) } widthsv <- convertUnit(lg$widths + x$padding.h, "mm", valueOnly = TRUE) heightsv <- convertUnit(lg$heights + x$padding.v, "mm", valueOnly = TRUE) widthsv[1] <- widthsv[1] * as.numeric(x$show.rownames) widths <- unit(widthsv, "mm") heightsv[1] <- heightsv[1] * as.numeric(x$show.colnames) heights <- unit(heightsv, "mm") cells = viewport(name = "table.cells", layout = grid.layout(lg$nrow + 1, lg$ncol + 1, widths = widths, heights = heights)) pushViewport(cells) tg <- gridExtra:::arrangeTableGrobs(lg$lgt, lg$lgf, lg$nrow, lg$ncol, lg$widths, lg$heights, show.colnames = x$show.colnames, show.rownames = x$show.rownames, padding.h = x$padding.h, padding.v = x$padding.v, separator = x$separator, show.box = x$show.box, show.vlines = x$show.vlines, show.hlines = x$show.hlines, show.namesep = x$show.namesep, show.csep = x$show.csep, show.rsep = x$show.rsep) upViewport() }
By defining this function in a global environment, it will take precedence over what's in gridExtra . This will allow us to configure the table before it is drawn, and our changes will not get reset. Here is the code for changing the colors of the values ββin the first two lines as you wish.
mytable = as.table(matrix(c("1","2","3","4","5","6","7","8"),ncol=2,byrow=TRUE)) mytable = tableGrob(mytable,gpar.coretext = gpar(col = "black", cex = 1)) mytable$lg$lgt[[7]]$gp$col <- "red" mytable$lg$lgt[[12]]$gp$col <- "blue" mydf = data.frame(x = 1:10,y = 1:10) ggplot( mydf, aes(x, y)) + annotation_custom(mytable)
And that creates this plot.

So the syntax is a little cryptic, but let me explain with this line
mytable$lg$lgt[[7]]$gp$col <- "red"
The mytable object is really just a decorated list. It has an lg element that is computed from makeTableGrobs and contains all the original grid elements inside. The lgt element below this is a different list with all text layers. For this table, lgt has 15 elements. One for each square in the table, starting with "empty" in the upper left corner. They go in order from top to bottom, from left to right, so cell 1 is [[7]] in the list. If you run str(mytable$lg$lgt[[7]]) , you can see the properties that make up this text grob. You will also notice a section for gp where you can set the color of the text using the col element. Therefore, we change it by default to black to the desired red.
What we do is not part of the official API, so it should be considered a hack and, as such, can be fragile for future changes in the libraries used ( ggplot2 , grid , gridExtra ). But hopefully this will at least help you get started setting up your table.