I'm looking for a way to add minor tick marks to ggplots without using coord_cartesian(clip = "off"). Or a way to reproducible apply clipping to the x axis but not the y axis, or vice versa. 
So far I have been using the annotation_ticks() function and the GeomTicks geom defined in this excellent answer here (with some minor modifications to make it work with ggplot2 v3.3.0). Unfortunately, for the tick mark annotations to appear on the outside of the plot one must use coord_cartesian(clip = "off") which means anything else that sits outside the plotting area is also exposed (see reprex below).
Alternatively, perhaps there is a way to leverage any of the new capabilities of ggplot2 v3.3.0 to draw minor ticks not as an annotation but as an actual part of the axis/plot so that it is possible to draw them outside the plotting area.
I am not a software developer but perhaps one could define a new theme element using register_theme_elements called axis.minor.ticks that behaves like axis.ticks but gets the appropriate location of the minor ticks from panel_params$y$break_positions_minor instead of panel_params$y$break_positions. Or somehow use the new guide_x() S3 functions.
Any help would be much appreciated!
Annotation function and ggproto object
The annotation_ticks() function (incorporating this fix for faceting issue):
annotation_ticks <- function(sides = "b",
                             scale = "identity",
                             scaled = TRUE,
                             ticklength = unit(0.1, "cm"),
                             colour = "black",
                             size = 0.5,
                             linetype = 1,
                             alpha = 1,
                             color = NULL,
                             ticks_per_base = NULL,
                             data = data.frame(x = NA), 
                             ...) {
  if (!is.null(color)) {
    colour <- color
  }
  # check for invalid side
  if (grepl("[^btlr]", sides)) {
    stop(gsub("[btlr]", "", sides), " is not a valid side: b,t,l,r are valid")
  }
  # split sides to character vector
  sides <- strsplit(sides, "")[[1]]
  if (length(sides) != length(scale)) {
    if (length(scale) == 1) {
      scale <- rep(scale, length(sides))
    } else {
      stop("Number of scales does not match the number of sides")
    }
  }
  base <- sapply(scale, function(x) switch(x, "identity" = 10, "log10" = 10, "log" = exp(1)), USE.NAMES = FALSE)
  if (missing(ticks_per_base)) {
    ticks_per_base <- base - 1
  } else {
    if ((length(sides) != length(ticks_per_base))) {
      if (length(ticks_per_base) == 1) {
        ticks_per_base <- rep(ticks_per_base, length(sides))
      } else {
        stop("Number of ticks_per_base does not match the number of sides")
      }
    }
  }
  delog <- scale %in% "identity"
  layer(
    data = data,
    mapping = NULL,
    stat = StatIdentity,
    geom = GeomTicks,
    position = PositionIdentity,
    show.legend = FALSE,
    inherit.aes = FALSE,
    params = list(
      base = base,
      sides = sides,
      scaled = scaled,
      ticklength = ticklength,
      colour = colour,
      size = size,
      linetype = linetype,
      alpha = alpha,
      ticks_per_base = ticks_per_base,
      delog = delog,
      ...
    )
  )
}
The ggproto object (now works with ggplot2 v3.3.0):
GeomTicks <- ggproto(
  "GeomTicks", Geom,
  extra_params = "",
  handle_na = function(data, params) {
    data
  },
  draw_panel = function(data,
                        panel_scales,
                        coord,
                        base = c(10, 10),
                        sides = c("b", "l"),
                        scaled = TRUE,
                        ticklength = unit(0.1, "cm"),
                        ticks_per_base = base - 1,
                        delog = c(x = TRUE, y = TRUE)) {
    ticks <- list()
    for (s in 1:length(sides)) {
      if (grepl("[b|t]", sides[s])) {
        # for ggplot2 < 3.3.0 use: xticks <- panel_params$x.minor
        if (utils::packageVersion("ggplot2") >= "3.2.1.9000") {
          x_minor_breaks <- panel_scales$x$break_positions_minor()
          x_major_breaks <- panel_scales$x$break_positions()
        } else {
          x_minor_breaks <- panel_scales$x.minor
          x_major_breaks <- panel_scales$x.major
        }
        xticks <- setdiff(x_minor_breaks, x_major_breaks)
        # Make the grobs
        if (grepl("b", sides[s])) {
          ticks$x_b <- with(
            data,
            segmentsGrob(
              x0 = unit(xticks, "npc"),
              x1 = unit(xticks, "npc"),
              y0 = unit(0, "npc"),
              y1 = ticklength,
              gp = gpar(
                col = alpha(colour, alpha),
                lty = linetype,
                lwd = size * .pt
              )
            )
          )
        }
        if (grepl("t", sides[s])) {
          ticks$x_t <- with(
            data,
            segmentsGrob(
              x0 = unit(xticks, "npc"),
              x1 = unit(xticks, "npc"),
              y0 = unit(1, "npc"),
              y1 = unit(1, "npc") - ticklength,
              gp = gpar(
                col = alpha(colour, alpha),
                lty = linetype,
                lwd = size * .pt
              )
            )
          )
        }
      }
      if (grepl("[l|r]", sides[s])) {
        # for ggplot2 < 3.3.0 use: yticks <- panel_params$y.minor
        if (utils::packageVersion("ggplot2") >= "3.2.1.9000") {
          y_minor_breaks <- panel_scales$y$break_positions_minor()
          y_major_breaks <- panel_scales$y$break_positions()
        } else {
          y_minor_breaks <- panel_scales$y.minor
          y_major_breaks <- panel_scales$y.major
        }
        yticks <- setdiff(y_minor_breaks, y_major_breaks)
        # Make the grobs
        if (grepl("l", sides[s])) {
          ticks$y_l <- with(
            data,
            segmentsGrob(
              y0 = unit(yticks, "npc"),
              y1 = unit(yticks, "npc"),
              x0 = unit(0, "npc"),
              x1 = ticklength,
              gp = gpar(
                col = alpha(colour, alpha),
                lty = linetype, lwd = size * .pt
              )
            )
          )
        }
        if (grepl("r", sides[s])) {
          ticks$y_r <- with(
            data,
            segmentsGrob(
              y0 = unit(yticks, "npc"),
              y1 = unit(yticks, "npc"),
              x0 = unit(1, "npc"),
              x1 = unit(1, "npc") - ticklength,
              gp = gpar(
                col = alpha(colour, alpha),
                lty = linetype,
                lwd = size * .pt
              )
            )
          )
        }
      }
    }
    gTree(children = do.call("gList", ticks))
  },
  default_aes = aes(colour = "black", size = 0.5, linetype = 1, alpha = 1)
)
Graph with coord_cartesian(clip = "on")
Column with very thick line width looks fine but can't see tick annotations.
library(ggplot2)
library(grid)
ggplot(mpg, aes(x = class, y = displ, fill = class)) + 
  stat_summary(fun = mean, geom = "col", colour = "black", size = 1) + 
  theme_classic(base_size = 8) + 
  scale_y_continuous(limits = c(0, 8), expand = c(0, 0)) + 
  annotation_ticks(sides = "l", ticklength = -1 * unit(0.2, "cm")) + 
  coord_cartesian(clip = "on")
ggsave("clip_on.png", device = "png", width = 4, height = 3)
Graph with coord_cartesian(clip = "off")
Tick annotations visible but columns with very thick line width are showing outside of the plotting area.
ggplot(mpg, aes(x = class, y = displ, fill = class)) + 
  stat_summary(fun = mean, geom = "col", colour = "black", size = 1) + 
  theme_classic(base_size = 8) + 
  scale_y_continuous(limits = c(0, 8), expand = c(0, 0)) + 
  annotation_ticks(sides = "l", ticklength = -1 * unit(0.2, "cm")) + 
  coord_cartesian(clip = "off")
ggsave("clip_off.png", device = "png", width = 4, height = 3)
 
    
